最可靠的方式是为每个部门创建独立数据库和专属账号并精确授权。例如创建dept_finance库和finance_app账号,仅授予其对dept_finance.*的SELECT/INSERT/UPDATE权限,host限定为'10.20.30.%',禁用通配符和'%'。

用 GRANT 限定数据库 + 部门专属账号是最可靠的方式
MySQL 没有“部门”原生概念,靠权限系统硬隔离。每个部门必须配独立账号和独立数据库,例如 dept_finance 和 dept_hr,再执行:
CREATE DATABASE dept_finance;CREATE USER 'finance_app'@'10.20.30.%' IDENTIFIED BY 'strong-pass';GRANT SELECT, INSERT, UPDATE ON dept_finance.* TO 'finance_app'@'10.20.30.%';
关键点:不能用 ON *.* 或 ON `dept_%`.* 这类模糊授权;host 必须写具体网段(如 '10.20.30.%'),禁用 '%';执行完无需 FLUSH PRIVILEGES —— GRANT 自动生效。
共享库中靠 dept_id 字段做逻辑隔离,但风险极高
如果必须共用一个库(比如 company_db),所有表加 dept_id 字段,并在每次查询时强制带上 WHERE dept_id = ?。但这依赖应用层 100% 正确实现,漏一次就全盘失效。常见崩坏场景包括:
- ORM 自动生成的
COUNT(*)或SELECT *语句没加dept_id条件 - LEFT JOIN 多表时,只在主表加条件,关联表数据未过滤
- MyBatis 的
<include></include>片段被多个 mapper 复用,但只有部分 mapper 注入了dept_id
此时哪怕 SHOW DATABASES 只看到自己库名,也挡不住一条没过滤的 SQL 扫出全部部门数据。
别信视图能自动隔离部门数据
有人想建视图 CREATE VIEW finance_orders AS SELECT * FROM orders WHERE dept_id = @current_dept,指望靠 SET @current_dept = 101 切换。这在生产环境几乎必败:
- 连接池复用连接后,
@current_dept值残留,上个请求设的值可能被下个部门用户继承 - 视图定义里变量未初始化时为
NULL,WHERE dept_id = NULL永远不成立,查不到任何数据 - MySQL 5.7 / 8.0.23 及以前版本不支持视图参数,
SQL SECURITY INVOKER也救不了
MySQL 8.0.24+ 虽支持带参函数(如 tenant_orders('f101')),但需全栈配合:应用传参、账号有基表 SELECT 权限、且不能降级兼容旧版本。
跨部门查数据只能走只读视图 + 显式授权
财务要查人事的在职员工数?不能直接给 finance_app 授 SELECT 权限到 hr_db.users 表。正确做法是:
- 在
hr_db里建视图:CREATE VIEW hr_active_users AS SELECT id, name, dept_name FROM users WHERE status = 'active'; - 再授视图权限:
GRANT SELECT ON hr_db.hr_active_users TO 'finance_app'@'10.20.30.%';
这样既满足业务,又卡死了手机号、身份证、薪资等字段。注意:视图字段必须显式列出,不能写 SELECT * —— 否则未来 users 表加敏感列,视图会自动透出。
实际部署中最容易被忽略的是连接建立后的初始库指定。很多应用连上 MySQL 后靠 USE hr_db 切库,但如果连接池没配置 connectionInitSql=USE hr_db,某次连接复用时可能还在 finance_db 上执行了对 hr_db.users 的裸查 —— 这种错误不会报错,只会静默返回空或错数据。











