如何使用Prisma ORM操作同一服务器上的多个MySQL数据库
Prisma 同服务器多MySQL库按用户身份动态切换实现方案
核心思路很直接:所有数据库部署在同一MySQL实例上,账号、主机、端口配置完全一致,仅库名不同,不需要做多数据源的复杂适配,只需要实现请求级别动态绑定对应用户的数据库连接即可,不要用全局固定的单PrismaClient实例。
1. 维护统一的Schema基线
所有用户对应的数据库表结构完全一致,你只需要维护一份基础schema.prisma即可,数据源地址先用环境变量占位:
datasource db { provider = "mysql" url = env("DATABASE_URL") } // 所有业务Model定义和单库项目完全一致,不需要针对多租户做特殊修改 model Order { id Int @id @default(autoincrement()) userId Int amount Decimal createTime DateTime @default(now()) }
后续做表结构迭代时,用这份基线生成迁移脚本,批量同步到所有用户的业务库即可,不需要每个库单独维护Schema。
2. 带缓存的动态Prisma实例工厂
写一个工厂方法,传入用户对应的库名动态生成Prisma实例,同时加全局缓存避免重复创建实例撑爆数据库连接:
import { PrismaClient } from '@prisma/client' // 缓存Map:key为业务库名,value为对应库的PrismaClient实例 const prismaClientCache = new Map<string, PrismaClient>() /** * 获取指定用户对应业务库的Prisma实例 * @param dbName 用户绑定的业务库名 */ export function getTenantPrisma(dbName: string) { // 命中缓存直接返回已有实例 if (prismaClientCache.has(dbName)) { return prismaClientCache.get(dbName)! } // 同MySQL实例下仅替换连接串里的库名部分,其余连接配置复用 // 注意提前给MySQL业务账号分配所有业务库的对应操作权限 const connectionUrl = `mysql://你的业务账号:你的密码@数据库内网IP:3306/${dbName}?connection_limit=5` const prisma = new PrismaClient({ datasourceUrl: connectionUrl }) prismaClientCache.set(dbName, prisma) return prisma }
注意:给MySQL账号授权时遵循最小权限原则,仅分配业务需要的CRUD、表结构变更权限即可,不要给跨库查询、超级管理员权限,避免越权风险。
3. 请求层拦截绑定对应实例
不管你用Express、Koa、NestJS还是其他后端框架,在鉴权中间件之后加一层租户上下文绑定中间件,逻辑固定三步:
- 从已解析的用户身份信息(Token/Session)中拿到当前请求的用户ID
- 从公共管理库查询该用户绑定的业务库名(公共管理库单独部署,用全局单例PrismaClient连接,仅存用户和库名的映射关系,不存业务数据)
- 将对应业务库的Prisma实例挂载到当前请求上下文,后续业务逻辑直接从上下文取实例操作即可,禁止硬编码库名
以Express中间件为例:
// 提前初始化公共管理库的全局单例Prisma,只用来查租户映射关系 import { PrismaClient } from '@prisma/client' const adminPrisma = new PrismaClient({ datasourceUrl: 'mysql://管理账号:管理密码@数据库IP:3306/tenant_admin' }) async function tenantBindMiddleware(req, res, next) { try { const currentUserId = req.auth.userId // 上游鉴权中间件已经解析好的登录用户ID // 查询用户绑定的库名 const tenantInfo = await adminPrisma.tenantConfig.findUnique({ where: { userId: currentUserId } }) if (!tenantInfo) { return res.status(403).send('当前用户无对应业务库访问权限') } // 把对应库的Prisma实例挂到请求对象上,业务层直接调用req.prisma操作即可 req.prisma = getTenantPrisma(tenantInfo.dbName) next() } catch (err) { next(err) } } // 全局注册中间件 app.use(tenantBindMiddleware)
常见避坑点
- 不要图省事用
$queryRaw写跨库SQL(比如select * from 库名.表名的形式),后续迁移、权限控制、问题排查都会非常麻烦,严格走请求上下文绑定的实例操作。 - 提前算好MySQL连接数上限:每个Prisma实例的
connection_limit不要设太高,比如单实例设5个连接,如果你有100个业务库,总连接数就是500,要提前调整MySQL服务端的max_connections参数,避免连接打满。 - 新用户注册流程里直接完成对应业务库的创建、基线迁移执行,不要等用户第一次发请求的时候才初始化库,会拖慢接口响应速度。
- 如果是Serverless环境部署,要给Prisma实例配置合理的连接空闲超时时间,避免僵尸连接占用数据库资源。
内容的提问来源于stack exchange,提问作者Mauricio
相关产品推荐
相关产品推荐

