只读用户必须显式撤销写权限再授予select权限,仅grant select不禁止已有写操作;需检查show grants、revoke残留权限、逐库授权、注意information_schema细粒度限制、确认user@host匹配及flush privileges生效。

只读用户必须显式拒绝写权限,不能只靠 GRANT SELECT
MySQL 的权限模型是“显式授权”,GRANT SELECT 不代表自动禁止 INSERT/UPDATE/DELETE;如果用户之前被授予过全局或库级写权限(比如通过 GRANT ALL PRIVILEGES),仅执行 GRANT SELECT 不会撤销已有权限。必须先 REVOKE 写操作权限,再 GRANT SELECT。
常见错误现象:用户仍能执行 DELETE FROM t1,即使你只运行了 GRANT SELECT ON db1.* TO 'ro_user'@'%' —— 很可能该用户继承了 mysql.user 表中残留的旧权限。
- 检查当前权限:运行
SHOW GRANTS FOR 'ro_user'@'%' - 彻底清理:先执行
REVOKE INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, INDEX, LOCK TABLES, EXECUTE ON *.* FROM 'ro_user'@'%' - 再授只读:
GRANT SELECT ON `db1`.* TO 'ro_user'@'%' - 最后别忘:
FLUSH PRIVILEGES
跨库查询时只读权限不自动继承
SELECT 权限是按库/表粒度控制的。用户对 db1 有 SELECT 权限,不代表能查 db2,也不代表能查 db1 中的 INFORMATION_SCHEMA 或 performance_schema —— 这些系统库需单独授权(通常不建议开放)。
使用场景:应用连接后执行 SELECT * FROM db2.t2 报错 ERROR 1142 (42000): SELECT command denied to user,但用户明明在 db2 上跑过 USE db2。
- 若需跨多个业务库只读,逐个授权:
GRANT SELECT ON `db2`.* TO 'ro_user'@'%' - 避免用
GRANT SELECT ON *.*(等同于所有库可读,含敏感系统库) - 注意反引号:库名含短横线或关键字时必须用
`db-name`,否则语法报错
只读用户无法执行 SHOW CREATE TABLE?检查 USAGE + SELECT 组合
很多 ORM 或监控工具会尝试执行 SHOW CREATE TABLE t1 或 SHOW FULL COLUMNS FROM t1。这类语句底层依赖 SELECT 权限(查 information_schema 表),但 MySQL 8.0+ 对 information_schema 的访问受更细粒度限制。
典型错误:用户能 SELECT * FROM t1,但执行 SHOW CREATE TABLE t1 报 ERROR 1142 (42000): SHOW command denied to user。
- MySQL 5.7:确保用户对
information_schema有SELECT(默认所有用户都有,除非显式REVOKE过) - MySQL 8.0+:
information_schema默认不可写,但部分元数据操作需USAGE+ 显式SELECT;稳妥做法是加一句:GRANT SELECT ON `information_schema`.`TABLES` TO 'ro_user'@'%' - 注意:不要授
information_schema全库,只需TABLES、COLUMNS、STATISTICS等必要表
应用连接后仍报权限错误?检查 host 匹配和 SQL_MODE
权限记录由 User+Host 共同决定。'ro_user'@'localhost' 和 'ro_user'@'%' 是两个完全独立的账号,哪怕密码相同,权限也互不影响。另外,某些 SQL_MODE(如 STRICT_TRANS_TABLES)虽不直接影响权限,但会让隐式类型转换失败,误判为“无权访问”。
- 确认连接来源 IP 是否匹配
Host字段(用SELECT USER(), CURRENT_USER()查看实际匹配的账号) - 测试时用
mysql -u ro_user -h 127.0.0.1 -p(走 TCP)而非mysql -u ro_user -p(走 socket,默认 host= localhost) - 检查是否启用了
read_only=ON全局设置:它只限制非 SUPER 用户的写操作,但不会替代细粒度权限控制;两者应配合使用,而非互相替代
复杂点在于权限生效链路:SQL 解析 → 账号匹配 → 权限查表(mysql.tables_priv / mysql.columns_priv / mysql.db)→ 缓存校验。中间任何一环配置偏差,都会导致“明明授了权却没效”。最易忽略的是 CURRENT_USER() 返回值与预期不符,以及未执行 FLUSH PRIVILEGES 后直接测试。










