☰
角色不同数据库账号不同:多数据源权限隔离与路由实现详解
2026/9/28 13:34:51 网站建设 项目流程

先说我看到这个标题时的第一反应:“变态需求”这四个字,特别像每次评审会上产品经理说完需求之后,后端同学心里默念但又不敢出声的那句话。角色不同,访问数据库的用户不同,听起来像是在给DBA和架构师找麻烦,但说真的,这类需求我接过的次数不少,它一点都不变态。它背后往往站着三类人:做安全审计的、做SaaS多租户的、以及集团内部做系统隔离的。你没看错,这个需求真正的本质,就是数据库权限隔离和数据责任追溯。

为什么这么说?因为绝大多数业务系统跑起来之后,Java后端、PHP后端或者Python后端,数据库连接串里通常只配了一个账号,所有角色共用同一个数据库用户。开发偷懒、运维省事,但一旦出了数据问题,你根本说不清楚是谁干的。“某个运营删了一批数据”“某个客服改了别人订单的状态”,这类事故翻查起来,只能看应用日志,而数据库层面的审计记录全是一片空白。所以角色访问数据库用户不同,解决的恰恰是“谁做了什么”的归属问题。

这篇文章不整虚的,直接把需求拆开、把方案铺开、把坑填平。内容覆盖MySQL、SQL Server、PostgreSQL的常见做法,偏向Java后端连接池路由的实现,但对PHP、Python同样有参考价值。适合正在做权限改造、被审计追着跑的团队,也适合想把项目从“单账号一把梭”升级到正规军架构的开发者。

1. 先把这个“变态需求”拆开看

1.1 需求背后到底想要什么

表面需求一句话:登录系统的人角色不同,后端连数据库时用的账号不同。比如管理员连的是admin_user,运营连的是operator_user,普通用户连的是member_user。但你要往深了想,这一句话背后藏着的其实是四个层次的数据管控诉求。

第一层是权限边界控制。不同角色能对数据做什么,不能做什么,不应该只在代码里加if判断,而应该在数据库账号层就掐死。比如运营账号只能select和insert,不能delete,管理员账号才有全部权限。这样即使应用层代码被人打穿,数据库账号权限也是最后一道防线。

第二层是责任追溯。每个库账号对应一类业务角色,出了问题直接查这条连接干了什么,不用再翻半天日志去猜是哪个用户在操作。我之前接过一个金融类项目,审计要求每一个操作都要能追溯到人,最后就是靠“人-角色-数据库账号”三层映射搞定的。

第三层是多租户隔离。你做的如果是SaaS产品,A公司的数据不能让B公司的账号看到,那光靠业务表里的org_id字段过滤是远远不够的。给每个租户分配独立的数据库账号,甚至独立的schema和库,才是合规的做法。

第四层是系统可用性和稳定性。业务大的时候,不同角色的负载模型完全不一样。只读报表角色的请求量大,写操作角色的请求量大,混在一个账号里,一个慢查询能把所有业务拖死。账号分开之后,连接池也分开,互相不干扰。

所以别再觉得这个需求变态了。它变态的地方只是实现成本,而它带来的价值——安全、审计、稳定性——恰恰是很多团队在事故之后才追悔莫及的东西。

1.2 三种实现方案,我为什么推荐“多数据源”

想清楚需求本质之后,真正要设计的是实现路径。我在项目里见过三种主流做法,也给读者先做个对照:

方案实现方式优点缺点适用场景
A. 应用层逻辑隔离数据库统一账号,代码里根据角色过滤数据改动最小,开发快权限在数据库层不隔离,风险高;审计几乎为零内部小系统、原型验证
B. 每个角色独立客户端连接前端/业务代码处直接为不同角色建立独立数据库连接连接角色清晰,权限隔离彻底连接管理成本高,后端要维护多套连接信息,容易乱低并发、工具型软件
C. 多数据源连接池 + 动态路由应用启动时注册多个数据源,运行时按角色路由到对应连接池连接复用效率高,权限隔离与性能兼顾需要引入路由机制,切换逻辑要小心绝大多数生产环境

方案A最省事,但后患无穷。CPU高的时候你想kill一个会话,结果发现所有业务都挂在同一个账号下,你根本分不清哪个连接是哪个角色的。SQL写错了想查是谁提交的,数据库层毫无痕迹。更尴尬的是,如果用户管理模块的设计有漏洞,一个普通用户拿到了系统管理员的接口权限,他在数据层的权力和真正的管理员没有任何区别。这就是典型的“锁门不锁保险柜”。

方案B听起来很“符合需求”,但实际做起来相当痛苦。每个角色都维护一套连接字符串,前端传个角色进来,后端再动态new一个连接。并发一高,连接数直接爆炸。而且不同角色的连接参数(超时、重试、SSL)还不好统一管理,我在外包项目里见过这么干的,最后数据库连接数把MySQL活活拖死。

方案C才是生产环境里真正能落地的路子。数据库账号按角色建好,应用层配置多个数据源,每个数据源对应一个连接池,运行时拿到当前用户的角色,动态选择该走哪个数据源。连接池复用机制保证了性能,账号隔离保证了安全和审计。下面几个章节,我们就按方案C往下抠细节。

2. 角色权限与数据库账号的映射设计

2.1 两层权限模型:业务角色到数据库账号

数据库账号的权限设计,核心是“最小权限原则”。说白了就是:只给够用的权限,多一个都嫌多。所以在动手建账号之前,你要先把业务角色梳理清楚,然后逐一映射到数据库账号。

以最常见的管理后台为例:

业务角色数据库账号库/表权限理由
超级管理员admin_user全部权限(含DDL)需要做结构变更、全局配置
运营专员operator_userSELECT、INSERT、UPDATE,无DELETE处理日常业务数据,但禁止删除
风控/审计audit_user只读SELECT只看数据,不碰数据
普通C端用户member_user限定表的SELECT、INSERT只能动自己的业务数据

这样一套映射关系出来之后,数据库账号直接对应业务角色的行为边界。运营想删数据?数据库层面直接拒绝,代码层再怎么写delete逻辑都是白搭。这是真正的“后端兜底”。

这里还要多说一句:别把数据库账号直接建成“人”的账号。比如张三叫zhangsan,李四叫lisi,名义上审计更精细了,但员工一离职,账号要改密码、要禁用,运维工作量直接翻倍。按角色建账号,员工变动只需要把人从角色里挪出去,账号和权限不用动。所以设计原则:账号跟着角色走,人跟着账号走。

2.2 账号创建与授权实操:以MySQL为例

很多同事一上来就写GRANT ALL PRIVILEGES ON *.* TO 'xxx'@'%',这个习惯得改掉。角色不同,权限范围必须掐死。下面这套SQL是我在项目里常用的模板,MySQL 5.7和8.0都兼容,你直接复制改一改就能用。

-- 1. 创建运营账号,只给CRUD中的C、R、U,不给D CREATE USER 'operator_user'@'%' IDENTIFIED BY 'StrongPass_2024'; GRANT SELECT, INSERT, UPDATE ON biz_db.* TO 'operator_user'@'%'; -- 如果某张表特别敏感,连update都不给,单独收窄 -- GRANT SELECT, INSERT ON biz_db.order_info TO 'operator_user'@'%'; -- GRANT SELECT, INSERT, UPDATE ON biz_db.user_info TO 'operator_user'@'%'; -- 2. 创建只读审计账号 CREATE USER 'audit_user'@'%' IDENTIFIED BY 'AuditPass_2024'; GRANT SELECT ON biz_db.* TO 'audit_user'@'%'; -- 3. 创建管理员账号(仅给需要的库,别给*.*) CREATE USER 'admin_user'@'%' IDENTIFIED BY 'AdminPass_2024'; GRANT ALL PRIVILEGES ON biz_db.* TO 'admin_user'@'%'; -- 如果要有结构变更能力,再单独给DDL权限 -- GRANT CREATE, ALTER, DROP, INDEX ON biz_db.* TO 'admin_user'@'%'; FLUSH PRIVILEGES;

有同学问:不是有CREATE ROLE吗?MySQL 8.0确实支持角色功能,可以先把权限集合定义成角色,再把角色赋给用户。比如:

CREATE ROLE 'biz_operator'; GRANT SELECT, INSERT, UPDATE ON biz_db.* TO 'biz_operator'; CREATE USER 'operator_user'@'%' IDENTIFIED BY 'StrongPass_2024'; GRANT 'biz_operator' TO 'operator_user'@'%'; SET DEFAULT ROLE ALL TO 'operator_user'@'%';

好处是把可复用的权限组抽象出来,以后再来一个新运营账号,一句GRANT 'biz_operator' TO ...就完事了。但要注意,MySQL的角色机制有个坑:角色赋给用户之后,默认情况下用户登录时角色是不激活的。必须设置SET DEFAULT ROLE ALL,或者在每次会话里执行SET ROLE ALL,否则你会发现明明授权了,查询表还是报权限不足。

如果是SQL Server,语法就换了一套。SQL Server的逻辑是先建登录名(login),再在数据库里建用户(user),最后把数据库角色或具体权限赋给用户:

-- SQL Server 2019+ CREATE LOGIN operator_login WITH PASSWORD = 'StrongPass_2024'; CREATE USER operator_user FOR LOGIN operator_login; -- 用固定数据库角色,简单粗暴 ALTER ROLE db_datareader ADD MEMBER operator_user; ALTER ROLE db_datawriter ADD MEMBER operator_user; -- 或者精确到schema级权限 GRANT SELECT, INSERT, UPDATE ON SCHEMA::dbo TO operator_user;

PostgreSQL那边又不太一样,它把“用户”和“角色”基本是统一的概念,CREATE ROLE出来的东西加上LOGIN属性就等价于用户:

-- PostgreSQL CREATE ROLE operator_user WITH LOGIN PASSWORD 'StrongPass_2024'; GRANT CONNECT ON DATABASE biz_db TO operator_user; GRANT USAGE ON SCHEMA public TO operator_user; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO operator_user;

记住一个原则:授权永远要精确到“库+表+操作类型”,而不是随手ALL PRIVILEGES。我见过太多开发环境里用root跑业务的团队,出事了别说追责,连恢复数据都费劲。

2.3 权限变更与账号生命周期管理

账号建好、授权完成,这只是开始。真正考验团队的,是后面“权限变更”和“员工离职”这两个场景。

先说权限变更。运营要开delete权限,你不能直接改线上账号,应该走变更流程:先在测试环境验证SQL,再在窗口期执行GRANT DELETE ON ...,做完之后通知应用侧刷新连接(后面会讲连接池的坑)。权限变更还意味着你要定期检查,我建议至少一个季度review一次线上账号,把那些半年没登录的僵尸账号直接禁用。

再说员工离职。前面说账号按角色建,好处在这里就体现了。人走了,只需要把登录系统的账号禁用,数据库账号密码不用改,因为他本来就没有专属数据库账号。但有一种情况例外:如果审计要求精确到人,你不得不给每个管理员单独建库账号,那离职时的操作清单就是:ALTER USER ... ACCOUNT LOCK,然后确认该账号的所有会话已经断掉,最后把相关的API授权全部回收。三步缺一不可,我在实际项目中就遇到过,人走了三个月,库账号还是活的,阿里云那边显示每天还有登录记录,查了半天是某个定时任务还在用旧连接串。

这里还要提醒一句:很多团队用了数据库同步软件或同步工具,主库到从库的同步通常会带上mysql库(系统库)。如果你给账号授权时用的是GRANT ... ON *.*,权限会写到系统库里,同步到从库之后,从库上的同一个用户权限也被改了。所以权限变更的时候,要确认同步链路是否包含系统库,别让从库的权限失控。

3. 连接层落地:多数据源路由这样写才不容易出错

3.1 为什么连接池必须分开

账号在数据库侧建好了,接下来是应用侧。项目里最忌讳的做法是这样的:在代码里根据角色手动DriverManager.getConnection()去连不同的库。这等于把连接生命周期管理直接扔给业务代码,并发一上去,连接风暴立刻教你做人。

正确的姿势是给每个账号配一个独立的数据源,也就是独立的连接池。这样每个池子各自管理自己的连接数量、超时时间、空闲回收策略。运营账号被慢查询拖累了,最多把运营池吃满,管理员账号和管理后台还是稳的。这就像办公楼里分了多个电梯,一台电梯坏了,不影响其他电梯上下班。

以Java后端为例,最方便的做法是用Spring Boot的dynamic-datasource-spring-boot-starter库。它底层封装了多数据源和AOP切换,配置写在application.yml里,代码只用一个注解或者一行API就能切换数据源,不用自己写AbstractRoutingDataSource。

3.2 配置与代码实现:按角色切数据源

先看配置,逻辑非常清晰:每一个数据源对应一个角色账号,连接串里的用户名密码各不相同。

spring: datasource: dynamic: primary: admin # 默认数据源,不指定时走这个 strict: false # 设置为true时,未匹配到数据源直接报错 datasource: admin: url: jdbc:mysql://10.0.0.1:3306/biz_db?useSSL=false&serverTimezone=Asia/Shanghai username: admin_user password: AdminPass_2024 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 minimum-idle: 5 max-lifetime: 1800000 connection-timeout: 30000 operator: url: jdbc:mysql://10.0.0.1:3306/biz_db?useSSL=false&serverTimezone=Asia/Shanghai username: operator_user password: StrongPass_2024 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 30 minimum-idle: 5 max-lifetime: 1800000 connection-timeout: 30000 audit: url: jdbc:mysql://10.0.0.1:3306/biz_db?useSSL=false&serverTimezone=Asia/Shanghai username: audit_user password: AuditPass_2024 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 10 minimum-idle: 2 max-lifetime: 1800000 connection-timeout: 30000

配置完之后,业务代码里用@DS注解就能切换:

// 管理员接口 @DS("admin") @GetMapping("/admin/config") public Result getConfig() { // 这里执行的所有SQL走admin数据源 } // 运营接口 @DS("operator") @PostMapping("/operator/order") public Result updateOrder(@RequestBody OrderDTO dto) { // 这里执行的所有SQL走operator数据源 } // 审计报表接口 @DS("audit") @GetMapping("/audit/report") public Result getReport() { // 这里执行的所有SQL走audit数据源 }

如果不想用注解,也可以手动指定:

DynamicDataSourceContextHolder.push("operator"); try { // 执行业务代码 } finally { DynamicDataSourceContextHolder.poll(); }

但是手动push/poll一定要放在try-finally里。我见过同事忘了poll,结果ThreadLocal里的数据源key一直没清掉,下一个请求走到同一个线程时,直接用了上一个请求的数据源。那个请求恰好是管理员的权限,普通用户莫名其妙就拿到了管理员数据源,这就是严重的越权事故。

3.3 连接复用与线程隔离:最容易踩的坑

多数据源方案有三类高频问题,项目组新同学几乎每批都踩一遍,我在这里集中说明。

第一类:事务内切换数据源无效。Spring的@Transactional一旦开启,事务管理器会在事务开始时从数据源拿一个连接绑定到当前事务上,之后这个事务里的所有SQL都走这个连接。你在事务方法里调DynamicDataSourceContextHolder.push("operator"),表面上是切了数据源,实际上SQL还是用事务开头的那个连接执行。更麻烦的是事务结束时的提交/回滚,如果连接和数据源不匹配,最终数据写到哪个库、回滚有没有生效,全靠命运安排。正确的做法是:先确定好一个事务走哪个数据源,把@DS注解和@Transactional放在同一个方法上,而且@DS只能是事务方法的注解,不能在内部再切换。

第二类:异步线程的数据源丢失。Spring的@Async会把任务丢到另一个线程执行,而DynamicDataSourceContextHolder用的是ThreadLocal,子线程根本继承不到父线程的数据源key。解决方法是手动传递,比如在提交任务前把数据源key拿出来,在异步方法入口重新设置。或者在异步方法上直接标注@DS("xxx"),让异步方法固定走某个数据源。我习惯用后者,因为异步任务的行为相对固定,按角色标记好就行。

第三类:连接池的连接不会因为权限变更而自动刷新。这是最隐蔽的坑。JDBC连接一旦建立,数据库端对该账号权限的修改不会实时同步到已存在的连接上。举个例子:运营账号本来没有delete权限,你给运营开了delete权限,正在运行的应用拿到的还是旧连接,这个连接可能在下一次请求时就执行了delete。反过来,你收回了某个权限,旧连接上依然能继续操作。所以权限变更之后,不只是FLUSH PRIVILEGES的问题,还要让连接池把旧连接淘汰掉。HikariCP里,max-lifetime默认是30分钟,意思是连接最长活30分钟就会重建。如果你等不了30分钟,可以主动重启服务,或者调用连接池的evictConnection相关方法把连接清了。

三句话总结:事务切源前想清楚、异步任务带好源、权限变更后重连。

4. 常见问题与排查实录

4.1 用户登录失败:Host不匹配和认证插件问题

多数据源上线第一周,最常见的就是应用日志里报Access denied for user 'operator_user'@'localhost'。这里有两个高频原因。

第一是Host匹配。MySQL的用户是由user和host共同确认的。你创建的时候写的是'operator_user'@'%',但应用服务器连接时,MySQL解析出来匹配的是'operator_user'@'localhost'或者'operator_user'@'10.0.0.1',如果恰好mysql.user表里有一个更精确的账号覆盖了匹配规则,就会用那个账号的密码去校验。记住MySQL host匹配的优先级:localhost> 具体IP > 网段 >%。排查时直接看mysql.user表:

SELECT user, host FROM mysql.user WHERE user = 'operator_user';

如果有多个host记录,确认应用连进来时走的到底是哪一条。最稳妥的方式是创建账号时直接指定IP或网段,别一上来就是%。

第二是认证插件。MySQL 8.0默认用caching_sha2_password,如果用老版本的驱动(比如5.1.x的Connector/J),会报Authentication plugin 'caching_sha2_password' cannot be loaded。解决办法:要么升级驱动到8.0.x,要么在创建用户时指定IDENTIFIED WITH mysql_native_password BY '...'。生产环境我强烈建议升级驱动,别为了省事把认证插件降级,新插件更安全。

4.2 权限变更不生效:不是缓存,是连接池

运营说“你给我加了delete权限,怎么还是删不了”。你先别急着怀疑数据库权限没刷上,检查一下应用日志那个时刻用的连接是不是旧连接。上面说过了,连接池里的长连接不会因为权限变更而自动升级。最直接的验证办法:SHOW PROCESSLIST看当前活跃连接是什么时间建立的。如果连接时间比你授权的时间还早,那一定是在用旧权限。

排查步骤:

  1. 用管理员账号执行SHOW PROCESSLIST,找到operator_user的会话,观察Time列。
  2. 如果连接都比较老,重启应用服务,让连接池重建。
  3. 重启后验证SELECT CURRENT_USER();和SHOW GRANTS FOR CURRENT_USER();,确认用的是哪个账号、有哪些权限。

这里还要提醒:GRANT之后其实不需要执行FLUSH PRIVILEGES,因为GRANT语句会直接修改系统权限表并刷新内存。只有你手动INSERT INTO mysql.user或者UPDATE mysql.user改了系统表,才需要FLUSH PRIVILEGES。用GRANT指令的同学别多此一举,当然执行了也没啥副作用,只是显得不专业。

4.3 越权风险:角色切换没生效的典型场景

这种问题的经典症状是:普通用户能访问管理员数据源里的数据。排查时先去数据库开general_log,看看那些可疑的SQL到底是哪个账号执行的。如果SQL确实是用member_user执行的,那问题在数据库权限没配好;如果SQL是用admin_user执行的,那问题一定在应用层路由。

应用层路由出问题,90%是ThreadLocal没清理。生产者消费者模型、线程池复用、异步回调,这些场景下如果DynamicDataSourceContextHolder里的key被错误地带到了下一个请求,且下一个请求没有自己的数据源key覆盖,就会出现串号。第二个高频原因就是前面说的:事务把数据源绑死了,@DS注解放在事务外面根本没生效,所有事务操作都走了默认数据源。

我最推荐的做法:开启dynamic数据源的strict: true。这样如果请求指定的数据源key不存在,直接抛异常,而不是悄悄退回默认数据源。宁可报错,也不要静默用错账号。

4.4 常见问题速查表

现象可能原因排查命令/手段解决动作
Access denied for userhost不匹配SELECT user,host FROM mysql.user;指定IP创建账号,核对连接来源
Authentication plugin错误驱动版本过旧查看应用日志特征码升级mysql-connector-java到8.x
权限变更后旧连接仍可越权操作连接池连接未重建SHOW PROCESSLIST查看连接Time重启应用或主动驱逐旧连接
普通用户能查管理员数据ThreadLocal数据源key串号开启strict模式试错检查异步/线程池场景,确认finally清理
@Transactional不生效/回滚异常事务内部切数据源打印连接对象hashCode把@DS放在事务方法上,禁止事务内切源
从库权限和主库不一致同步工具同步了mysql库对比主从mysql.user表过滤同步规则,排除系统库或统一管控

这套排查表基本能覆盖95%的“数据库账号多数据源”场景问题。剩下5%,大概率是网络层面(比如防火墙限制了新账号的IP)、密码策略(validate_password插件强制密码复杂度过高)或者权限申请流程不规范。遇到奇怪的报错,先看完整异常栈,别只盯着第一行。

5. 跨数据库与运维辅助:这活儿还能更顺一点

5.1 多数据库方言对照:一次设计,到处抄

每个团队用的数据库不一样,但设计思路是一致的。上面MySQL的实操最详细,这里把SQL Server和PostgreSQL的要点也列出来,方便读者对照。

SQL Server的权限模型分两级:服务器级登录名(login)和数据库级用户(user)。登录名能连上实例,但能不能看某个库的表,取决于数据库里有没有对应的user以及这个user的权限。所以按角色建账号的时候,两步缺一不可:

-- 1. 服务器层 CREATE LOGIN operator_login WITH PASSWORD = 'StrongPass_2024'; -- 2. 数据库层 USE biz_db; CREATE USER operator_user FOR LOGIN operator_login; -- 3. 授权(精确到schema) GRANT SELECT, INSERT, UPDATE ON SCHEMA::dbo TO operator_user;

PostgreSQL更简洁,角色和用户是一个概念,但要注意GRANT默认只对已存在的表生效。如果你给角色授权之后又新建了表,新表默认不会自动授权给该角色,需要设置默认权限:

ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE ON TABLES TO operator_user;

PostgreSQL的默认权限是个老朋友坑,很多人建好账号授权完,第二天业务一跑发现新表查询报permission denied for table,就是没设置默认权限。

Oracle的做法则更“重”,如果用企业版,可以开细粒度审计(Fine-Grained Auditing),直接把“哪个用户、什么时间、访问了哪些行”记录下来,这已经是合规级别的方案了。不过Oracle的授权体系复杂,需要专门写一篇,这里不展开。

5.2 批量脚本与常用工具:让账号管理不再靠手敲

线上十几个环境,每个环境五六个角色账号,全用手敲SQL容易漏,也容易错。我自己习惯的做法是准备一套可重复执行的脚本。Linux环境下,可以用一个简单的shell循环批量建账号:

#!/bin/bash # 批量创建只读账号脚本 MYSQL_CMD="mysql -h10.0.0.1 -uroot -p'RootPass'" for DB_NAME in biz_db_a biz_db_b biz_db_c; do $MYSQL_CMD -e "CREATE USER IF NOT EXISTS 'audit_${DB_NAME}'@'%' IDENTIFIED BY 'AuditPass_2024';" $MYSQL_CMD -e "GRANT SELECT ON ${DB_NAME}.* TO 'audit_${DB_NAME}'@'%';" done

这套脚本写完之后,保存到运维平台或者jenkins里,新环境初始化的时候直接跑一遍,账号权限全部就位。比人肉在Navicat或者DBeaver里一个个点,靠谱得多。

工具方面,日常管理建议用DBeaver这种免费且跨平台的客户端,能同时管理MySQL、PostgreSQL、SQL Server多个连接。数据库管理工具鱼龙混杂,但功能都大同小异,关键是养成习惯:连接别用root,操作前看清楚当前连接的是哪个账号。我自己就吃过一次亏,用root连接开发库,本想truncate临时表,结果手滑把整张业务表清了。从那以后,所有客户端一律只用只读账号登录,真正的写操作全部通过审核流程走发布平台执行。

5.3 从多账号到多租户:这个需求还能延伸出什么

角色不同访问数据库用户不同,这套机制再往前走一步,就是SaaS多租户的数据库隔离。

租户隔离通常有三个层级:共享库共享表、共享库独立Schema、独立库。落到数据库账号层面,可以选择给每个租户一个独立账号,配合current_setting(PostgreSQL)或CONNECTION_ID()加表前缀(MySQL)来区分数据。如果租户数量很大,也可以一组租户共用一个账号,再靠业务字段隔离。没有绝对正确的答案,核心看安全等级和成本预算。

另外,如果你的系统是那种报表查询特别重的场景,还可以把只读账号直接指向只读从库。一个账号对应一个数据源,数据源指向不同地址的从库,既实现了角色隔离,又完成了读写分离。一举两得。

最后再分享点个人经验

做了这么多年数据库和权限相关的东西,我的体会是:这种需求第一次做会觉得麻烦,但做完之后整个系统的安全感完全不一样。以前数据库账号混用的时候,每次出问题都跟破案一样,几十个人的团队谁都不认账;现在账号分开了,SQL一查就知道是哪个角色干的,连扯皮的机会都没有。

还有一个建议:账号权限的改动一定要留痕。哪怕团队里只有你一个人管数据库,也要习惯把所有CREATE USER、GRANT、REVOKE语句沉淀到Git仓库里,写成变更记录。不用太复杂,一个SQL文件夹加一份变更日志就够。哪天数据库被误操作了,你翻翻记录,五分钟就能定位是谁、什么时候、改了什么。

最后再提个实际小技巧:MySQL环境下,每次建完账号建议顺手执行一下SELECT user, host, plugin FROM mysql.user WHERE user = '你刚建的用户';,确认host和认证插件符合预期。别问我为什么强调这个,问就是吃过亏——大晚上的,生产环境报认证失败,查了半小时,发现是host写成了'%',但应用服务器网段正好被更具体的规则拦截了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询