mysql只读账号必须显式授予select权限并限制库表范围,禁用lock tables等隐式写权限,补usage确保登录,8.0+需显式授权information_schema.tables且设default role激活。

只读账号不是“没给写权限就自动只读”,而是必须显式授予 SELECT 权限、且不混入任何额外权限——漏掉一个 LOCK TABLES 或多授一个 SHOW VIEW,都可能绕过预期限制。
GRANT SELECT 必须指定库和表范围,不能靠“没给写权限”来假设安全
MySQL 权限是白名单机制:GRANT SELECT ON mydb.* 是唯一可靠起点;REVOKE INSERT, UPDATE 等操作不仅多余,还容易遗漏(比如漏掉 TRUNCATE 或 EXECUTE)。实际中常见误操作包括:
- 写成
GRANT SELECT ON *.*—— 这会把mysql、performance_schema等系统库也放开,哪怕查不到敏感数据,也会暴露库结构 - 用单引号包库名,如
'my-db'.*—— 语法错误,正确是`my-db`.*或无特殊字符时直接写mydb.* - 跨库 JOIN 查询失败,却以为权限没问题 ——
SELECT * FROM db1.t1 JOIN db2.t2要求对db1和db2都显式执行GRANT SELECT ON db1.*和GRANT SELECT ON db2.*
新用户连不上?大概率缺了 USAGE 权限
GRANT SELECT 只管“能做什么”,不管“能不能登录”。没有 USAGE,用户认证通过后立刻断连,报错 ERROR 1045 (28000): Access denied,哪怕密码完全正确。
解决方法很简单,补一条:
GRANT USAGE ON *.* TO 'ro_user'@'192.168.10.%';
注意:USAGE 是连接权,不带任何操作能力,必须和 SELECT 分开授予。MySQL 8.0+ 不再自动附带 USAGE,这步不能跳。
SHOW CREATE TABLE 失败?不是权限没给全,是缺 INFORMATION_SCHEMA 显式授权
很多 ORM、监控工具或 DBA 手动查表结构时会执行 SHOW CREATE TABLE t1,这条语句底层依赖对 information_schema.TABLES 和 information_schema.COLUMNS 的 SELECT 权限。
MySQL 5.7 默认允许所有用户访问 information_schema,但 8.0+ 默认收紧。如果只授了业务库权限,SHOW CREATE TABLE 就会报 ERROR 1142 (42000): SELECT command denied。
稳妥做法是加一句:
GRANT SELECT ON `information_schema`.`TABLES` TO 'ro_user'@'192.168.10.%';
不需要开放整个 information_schema,只给 TABLES 和 COLUMNS 就够用。别顺手写成 GRANT SELECT ON information_schema.* —— 这会暴露更多元数据。
MySQL 8.0+ 推荐用角色管理,但 DEFAULT ROLE 不是默认生效
角色能解耦权限与用户,改权限不用逐个用户操作。创建只读角色的标准流程是:
CREATE ROLE 'readonly_role';<br>GRANT SELECT ON `myapp_db`.* TO 'readonly_role';<br>GRANT 'readonly_role' TO 'ro_user'@'192.168.10.%';
关键点在最后一步:SET DEFAULT ROLE 'readonly_role' TO 'ro_user'@'192.168.10.%';。这个命令在 MySQL 8.0.16+ 才真正让角色登录即激活;低于该版本,用户得手动执行 SET ROLE 'readonly_role';,否则权限不生效。
验证是否启用成功:用该用户登录后运行 SELECT CURRENT_ROLE();,返回 'readonly_role' 才算到位。
最常被忽略的其实是 host 匹配细节——'ro_user'@'localhost' 和 'ro_user'@'127.0.0.1' 是两个账号,用 socket 连和 TCP 连走的是不同匹配路径。上线前务必用 SELECT CURRENT_USER(); 看清楚到底匹配到了哪个账号。











