You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NestJS项目中结合Prisma实现数据库读写分离复制?

实现NestJS + Prisma + MySQL读写分离方案

一、先完成MySQL主从复制配置

主从复制是读写分离的基础,先确保主从库数据同步正常:

  • 主库(Master)配置

    1. 修改my.cnf,开启binlog并设置唯一标识:
      log-bin=mysql-bin
      server-id=1
      binlog-do-db=your_database_name
      
    2. 重启MySQL,创建复制专用账号并授权:
      CREATE USER 'repl_user'@'slave_ip' IDENTIFIED BY 'your_password';
      GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'slave_ip';
      
    3. 锁表导出主库数据(避免导出过程中数据变更):
      FLUSH TABLES WITH READ LOCK;
      
      用mysqldump导出数据库后执行UNLOCK TABLES;解锁。
  • 从库(Slave)配置

    1. 修改my.cnf,设置唯一标识和中继日志:
      server-id=2
      relay-log=relay-bin
      
    2. 导入主库的备份数据。
    3. 配置主库连接信息并启动复制:
      CHANGE MASTER TO MASTER_HOST='master_ip', MASTER_USER='repl_user', MASTER_PASSWORD='your_password', MASTER_LOG_FILE='mysql-bin.xxxxxx', MASTER_LOG_POS=xxx;
      START SLAVE;
      
    4. 用SHOW SLAVE STATUS\G检查Slave_IO_Running和Slave_SQL_Running是否为Yes,确认同步正常。

二、配置Prisma多数据源

修改schema.prisma,分别配置主库和从库的数据源:

generator client {
  provider = "prisma-client-js"
}

datasource db_master {
  provider = "mysql"
  url      = env("DATABASE_URL_MASTER")
}

datasource db_slave {
  provider = "mysql"
  url      = env("DATABASE_URL_SLAVE")
}

在.env文件中添加两个数据库连接地址:

DATABASE_URL_MASTER="mysql://user:password@master_host:3306/your_database"
DATABASE_URL_SLAVE="mysql://user:password@slave_host:3306/your_database"

重新生成Prisma客户端:

npx prisma generate

三、在NestJS中实现读写分离逻辑

1. 自定义Prisma服务

创建prisma.service.ts,初始化主、从库两个客户端:

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient as PrismaMasterClient } from '@prisma/client/db_master';
import { PrismaClient as PrismaSlaveClient } from '@prisma/client/db_slave';

@Injectable()
export class PrismaService implements OnModuleInit, OnModuleDestroy {
  public readonly master: PrismaMasterClient;
  public readonly slave: PrismaSlaveClient;

  constructor() {
    this.master = new PrismaMasterClient({
      log: ['query', 'warn', 'error'],
    });
    this.slave = new PrismaSlaveClient({
      log: ['query', 'warn', 'error'],
    });
  }

  async onModuleInit() {
    await this.master.$connect();
    await this.slave.$connect();
  }

  async onModuleDestroy() {
    await this.master.$disconnect();
    await this.slave.$disconnect();
  }
}

2. 业务层区分读写操作

在业务服务中,手动指定写操作走主库,读操作走从库:

import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { User } from '@prisma/client/db_master';

@Injectable()
export class UserService {
  constructor(private readonly prisma: PrismaService) {}

  // 读操作:使用从库
  async getUsers(): Promise<User[]> {
    return this.prisma.slave.user.findMany();
  }

  async getUserById(id: number): Promise<User | null> {
    return this.prisma.slave.user.findUnique({ where: { id } });
  }

  // 写操作:使用主库
  async createUser(data: Omit<User, 'id'>): Promise<User> {
    return this.prisma.master.user.create({ data });
  }

  async updateUser(id: number, data: Partial<User>): Promise<User> {
    return this.prisma.master.user.update({ where: { id }, data });
  }
}

3. 进阶:用拦截器自动路由(可选)

如果不想手动指定客户端,可写拦截器根据请求方法自动切换:

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { PrismaService } from './prisma.service';

@Injectable()
export class PrismaRoutingInterceptor implements NestInterceptor {
  constructor(private readonly prisma: PrismaService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    // POST/PUT/DELETE/PATCH 走主库,其余走从库
    if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method)) {
      request.prisma = this.prisma.master;
    } else {
      request.prisma = this.prisma.slave;
    }
    return next.handle();
  }
}

在模块中全局注册拦截器:

import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { PrismaRoutingInterceptor } from './prisma-routing.interceptor';
import { PrismaService } from './prisma.service';

@Module({
  providers: [
    PrismaService,
    {
      provide: APP_INTERCEPTOR,
      useClass: PrismaRoutingInterceptor,
    },
  ],
})
export class DatabaseModule {}

四、关键注意事项

  • 主从延迟:写操作后立即读可能遇到从库未同步的问题,这类场景需强制使用主库读取。
  • 从库故障:可配置多从库,在Prisma服务中加入健康检查和故障切换逻辑。
  • 权限控制:从库账号仅授予读权限,主库账号授予读写权限,提升安全性。

内容的提问来源于stack exchange,提问作者Rajib Dey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 22:30:51