创建只读账户需先CREATE USER建身份,再GRANT SELECT ON app_production.*限定库级权限;必须确保host精确匹配且不授*.*,否则SCHEMA隔离失效。

创建只读账户并绑定到指定SCHEMA
MySQL本身不支持“只读账户”这个概念,CREATE USER只是建身份,权限得靠GRANT显式给。想让第三方只能查某个库,必须两步:先建用户,再只授SELECT权且限定到具体SCHEMA(即数据库名)。
CREATE USER 'thirdparty_app'@'192.168.10.%' IDENTIFIED BY 'strong_pass_2024';GRANT SELECT ON `app_production`.* TO 'thirdparty_app'@'192.168.10.%';- 执行
FLUSH PRIVILEGES;生效(仅在修改mysql.user表直改时才必需;用GRANT通常不用)
注意:app_production必须已存在,否则GRANT会报错ERROR 1146 (42S02): Table 'mysql.db' doesn't exist——这是MySQL老版本的误导性错误,实际是目标SCHEMA不存在。
为什么不能用GRANT SELECT ON *.*再靠应用层过滤库名
第三方连接时若没指定USE app_production,又或代码里写了SELECT * FROM other_db.users,只要权限是*.*,这条语句就能跑通——SCHEMA限定失效。MySQL的权限检查发生在解析阶段,不依赖当前USE库。
- 权限粒度最小单位是
db.table,不是“连接后默认进哪个库” -
GRANT SELECT ON *.*等于放行所有库所有表的读,和“独立账户”目标直接冲突 - 某些ORM会自动加
database_name.table_name前缀,这时候更需要精确匹配GRANT范围
遇到Access denied for user但账号密码没错
大概率是主机名(host)不匹配。MySQL权限记录的是'user'@'host'成对值,'thirdparty_app'@'%'和'thirdparty_app'@'192.168.10.%'是两个不同账户。
- 查真实连接来源IP:
SELECT USER(), CURRENT_USER();——前者是客户端声明的用户,后者才是MySQL校验通过的实际账号 - 如果第三方走NAT或代理,
CURRENT_USER()可能显示'thirdparty_app'@'10.0.0.5',但你只给了'%'权限,而MySQL 8.0+默认禁用'%'匹配IPv6或带端口的地址 - 安全做法:明确写CIDR,比如
'thirdparty_app'@'192.168.10.0/24'(需MySQL 8.0.29+),或保守点用'thirdparty_app'@'192.168.10.%'
避免把INFORMATION_SCHEMA或performance_schema意外暴露
新用户默认看不到系统库,但一旦执行过GRANT ... ON *.*或误操作GRANT USAGE ON *.*,就可能继承全局USAGE权限,导致能连上但查INFORMATION_SCHEMA.TABLES看到所有库结构。
- 建完账户后立刻验证:
mysql -uthirdparty_app -p -e "SHOW DATABASES;"——正常应只列出app_production和information_schema(后者总是可见,但内容受权限限制) - 如果看到其他业务库,说明
GRANT范围太宽,删掉重来:DROP USER 'thirdparty_app'@'host'; - 别省事用
GRANT ALL PRIVILEGES,哪怕临时调试——ALL包含SHOW VIEW、LOCK TABLES等非必要权限,第三方SDK可能无意触发
真正麻烦的不是建账户,而是确认第三方连接时用的host和你GRANT时写的host完全一致,以及确保他们没在SQL里跨库引用——这两处漏掉,SCHEMA隔离就形同虚设。


















