创建只读用户需先确认MySQL版本及认证插件,8.0+默认caching_sha2_password可能不兼容旧客户端,应显式指定mysql_native_password;授权须限定具体业务库而非.,避免暴露系统库;注意host匹配、密码修改方式及CURRENT_USER验证;精细化控制可用视图封装字段并授视图权限;还需禁用secure_file_priv防范数据导出风险。
创建只读用户前先确认 MySQL 版本和认证插件
mysql 8.0+ 默认用 caching_sha2_password 插件,而老客户端(比如某些 python 2.7 的 mysqlclient)可能不兼容,连上去就报 authentication plugin 'caching_sha2_password' cannot be loaded。不是权限问题,是握手失败。
实操建议:
- 先查版本:
SELECT VERSION(); - 查用户插件:
SELECT user, host, plugin FROM mysql.user WHERE user = 'your_user'; - 如果要兼容旧客户端,建用户时显式指定:
IDENTIFIED WITH mysql_native_password BY 'xxx'
GRANT SELECT 时必须指定数据库和表范围,不能只写 *.*
很多人以为 GRANT SELECT ON *.* TO 'ro_user'@'%'; 就够了,其实这会把系统库(mysql、information_schema 等)也放开——不仅不安全,有些系统表(如 mysql.user)即使有 SELECT 权限,普通用户也查不到数据(返回空),反而让人误以为权限没生效。
实操建议:
- 只给业务库:比如
GRANT SELECT ON `myapp_db`.* TO 'ro_user'@'%'; - 如果要跨多个库,逐个授权,别偷懒用通配符;
mysql库一律不授 - 注意反引号:库名含短横或关键字(如
order)必须用`order_db`,否则语法错
刷新权限后仍连不上?检查 host 匹配和密码是否已生效
FLUSH PRIVILEGES; 不是万能的。MySQL 8.0+ 中,CREATE USER 和 GRANT 本身就会立即生效,再执行 FLUSH 没必要,还可能掩盖问题。更常见的卡点是:
- 用户 host 写成
'ro_user'@'localhost',但连接用的是127.0.0.1——这两个在 MySQL 里是不同账号 - 密码改了但没用
ALTER USER ... IDENTIFIED BY,而是直接 UPDATE mysql.user 表,导致哈希值格式不对 - 客户端用了 --socket 连接(默认走 localhost),但用户只允许 % 或具体 IP,不匹配
验证方式:SELECT CURRENT_USER(); 看实际匹配到哪个账号;SHOW GRANTS FOR CURRENT_USER; 看当前会话权限是否如预期。
想限制只读用户只能查特定几张表?用视图 + 权限组合更稳妥
MySQL 不支持对单张表的列级 SELECT 控制(比如只让查 users.name,不给 users.phone),也没办法直接 GRANT SELECT(name, email) ON users。硬要精细化,得绕一下。
实操建议:
- 建视图封装需要暴露的字段:
CREATE VIEW ro_users AS SELECT id, name, email FROM users; - 只给视图 SELECT 权限:
GRANT SELECT ON `myapp_db`.`ro_users` TO 'ro_user'@'%'; - 别忘了撤掉原表权限(如果之前授过):
REVOKE SELECT ON `myapp_db`.`users` FROM 'ro_user'@'%';
注意:视图依赖原表权限,但只要不给原表权限,用户就无法绕过视图直接查;不过视图定义里不能含子查询或临时表,否则某些客户端驱动解析会出问题。
最易被忽略的一点:只读不是靠“不给 INSERT/UPDATE”就万事大吉的——如果用户有 SELECT 权限,又知道表结构,就能用 SELECT ... INTO OUTFILE 把数据导出到服务器磁盘(需 FILE 权限配合),或者通过联合查询触发 UDF、SSRF 等边界行为。真要严控,得关掉 secure_file_priv,并定期审计 SHOW PROCESSLIST 里的长查询。










