
1. 先拆需求为什么建库和建用户从来不是一回事每次接手一个新项目我第一件事就是把数据库的账号体系理顺。建库、建用户、授权这三件事看着简单但我在不少团队里见过五花八门的写法有人直接拿root灌进业务代码里有人所有服务共用一个账号还有人建完库发现字符集不对整张表都是乱码。今天就把这条链路从头到尾捋一遍。很多新手拿到一台新装的MySQL第一反应就是登录root然后CREATE DATABASE再在业务代码里直接用root连接。这种玩法在本地开发环境没问题但一旦到了团队协作或者线上环境迟早要出事。我见过最典型的场景某次上线前检查发现整个项目所有服务共用一个root账号连备份脚本、报表同步、后台管理全都混在一起。后来某个组员在测试库里执行了DROP TABLE因为权限没隔离误删的是生产库的表。那次事故之后我们花了一整个晚上恢复数据代价极其惨痛。所以新建库和用户这件事本质上是两个独立的操作一个是创建数据存储的容器数据库一个是创建身份凭证账号。二者通过授权GRANT建立关联。MySQL的权限模型可以简单理解成账号是谁库是哪里权限是能干什么。账号本身不绑定库库本身也不绑定账号只有授权语句把二者连接起来。这个设计的好处是灵活——同一个账号可以访问多个库同一个库也可以被多个账号访问——但坏处也在这里如果授权时图省事给了全局权限那就等于把整个实例的门都打开了。这也是为什么我强烈建议每个应用单独建一个库、单独建一个账号并且只授予该库的权限。哪怕你的业务再小哪怕只是写个简单的接口这一步多花五分钟后面能省掉无数个晚上。另外还有一点容易忽略CREATE DATABASE 和 CREATE USER 这两个操作本身是需要权限的只有root或者拥有相应管理权限的账号才能执行。所以当你发现自己执行建库报错Access denied时先看看自己是不是拿了一个只读账号在操作而不是一上来就怀疑语法写错了。这个需求拆解的过程说白了就是回答三个问题你的数据放在哪里库谁有资格进来用户进来之后能做什么权限把这三件事想清楚再动手就不会出现建了一大堆库不知道哪个是哪个或者所有应用共用一个密码的尴尬局面。接下来我先讲清楚动手之前必须掌握的几个概念再带你走一遍完整的实操流程。2. 动手前必须搞清楚的三个知识点2.1 字符集与排序规则utf8mb4 基本是唯一选择建库的时候CREATE DATABASE 语法其实很简洁CREATE DATABASE 库名;但生产环境我从不这么裸写因为默认字符集不一定是你想要的。MySQL 5.6及更早版本默认是latin15.7之后默认是utf8mb4但如果你用某些云厂商的默认配置或者从老版本升级上来的实例默认值可能依然是latin1或者utf8注意是utf8不是utf8mb4。这里必须说清楚一个历史坑MySQL里的utf8其实是个假utf8它最多只能存3个字节存不下emoji4字节以及一些生僻汉字。utf8mb4才是真正的完整UTF-8。所以现在所有新建的库我都建议CREATE DATABASE 库名 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;或者更现代一点在MySQL 8.0里推荐CREATE DATABASE 库名 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;utf8mb4_unicode_ci 和 utf8mb4_0900_ai_ci 的差别在于排序规则的精确度和性能。0900_ai_ci 是8.0引入的新排序规则基于Unicode 9.0支持更准确的字母大小写和重音比较性能也更好。但如果你未来有跨版本迁移、或者命令行工具还在用旧版本_unicode_ci 的兼容性更稳。我的习惯是8.0实例用0900_ai_ci5.7及以下用unicode_ci图省心就统一unicode_ci不会出错。另一个容易忽略的点库的字符集一旦创建后续虽然可以用ALTER DATABASE修改但已经存在的表的字符集不会跟着变。所以建库之前就定好字符集别指望后面再改。如果是老项目迁移我通常会写一个脚本把所有表的字符集统一改一遍而不是只改库级别。2.2 认证插件5.7和8.0的分水岭MySQL 8.0默认的认证插件是 caching_sha2_password而5.7及更早版本是 mysql_native_password。这两者的区别直接影响你用什么客户端、什么驱动连接。caching_sha2_password 安全性更高但有个实际痛点老版本的驱动比如PHP的mysqlnd、Python的MySQLdb、一些版本较早的JDBC驱动不认识它连接时会直接报Authentication plugin caching_sha2_password cannot be loaded如果你确认驱动太老、一时半会还升级不了可以在创建用户的时候显式指定老插件CREATE USER userlocalhost IDENTIFIED WITH mysql_native_password BY 密码;不过这只是过渡方案。caching_sha2_password 是官方默认新项目尽量让驱动跟上来别为了省事把安全等级拉低。这个问题我在后面常见报错里会再展开。2.3 用户host字段你以为是主机地址其实是白名单MySQL用户的完整写法是用户名主机比如myapplocalhost和myapp%是不同的两个用户。这里的host不是我自己的IP而是允许从哪里来连接。常见取值host值含义适用场景localhost只允许本机连接脚本、本地管理工具、数据库与应用同机127.0.0.1本机TCP连接与localhost走socket不同需区分连接方式%任何地址图省事但风险最高不建议生产环境使用192.168.1.100单个IP指定的应用服务器最推荐192.168.1.%特定网段一组内网IP适合多台应用服务器我记得有次排查一个密码明明是对的但连不上的问题最后发现就是因为当时创建了myapplocalhost而应用服务器是通过内网IP去连数据库的MySQL匹配到了没有这个用户才报错。这个坑太典型了。所以先想清楚应用的连接路径再选host。如果数据库和应用在同一台机器用localhost如果分开部署建议精确到应用服务器的IP或网段而不是直接给%。3. 实操全流程从建库到授权的一步步操作3.1 连接MySQL实例一切操作前先用root登录mysql -u root -p输入密码后进入交互式命令行。也可以直接在命令里带密码不推荐因为会留在shell历史里mysql -u root -p密码如果你是通过云数据库控制台创建的实例通常会有一个默认的高权限账号用那个账号登就行。这里提醒一句root密码的管理一定要走公司密码管理流程别写在代码里别贴在明文文档里这是底线。3.2 第一步创建数据库假设业务系统叫商城我习惯的库名是shop简单、小写、语义清晰。完整建库语句CREATE DATABASE shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;验证一下SHOW CREATE DATABASE shop;应该能看到字符集和排序规则都变成了刚才指定的值。这里有个小细节库名为什么要用反引号因为反引号是MySQL的标识符引用符避免库名和保留字冲突。虽然shop不是保留字但养成用反引号的习惯没坏处特别是库名里万一出现order、group这类单词不加反引号直接语法错误。3.3 第二步创建用户接下来为这个库创建一个专用账号。假设应用服务器IP是192.168.1.50那就这样写CREATE USER shop_app192.168.1.50 IDENTIFIED BY 一个足够复杂的密码;如果是在本机调试或者确实不好确定IP可以暂时用shop_app%但我事后通常会收拢到具体网段。MySQL 8.0里创建用户的语法和5.7略有不同8.0里IDENTIFIED BY 密码默认就是 caching_sha2_password不需要额外指定。密码复杂度方面MySQL 5.7默认装了validate_password组件要求至少8位、包含大小写字母和数字。如果设置密码时报警就按提示改成更复杂的。不要为了省事把这个组件卸载生产环境密码复杂度是底线。3.4 第三步授权授权分两种粒度我分别说。只授一个库的所有操作权限最常用GRANT ALL PRIVILEGES ON shop.* TO shop_app192.168.1.50;这条语句的意思是允许这个用户在shop库下的所有表上执行所有操作SELECT、INSERT、UPDATE、DELETE、CREATE、ALTER、DROP等。但这里的ALL不包含GRANT OPTION也就是说这个用户不能把权限再转授给别人这正好是我们想要的——权限收敛。如果只给只读权限比如报表账号GRANT SELECT ON shop.* TO report_user192.168.1.%;如果只给某个表的权限比如日志账号只写某张表GRANT SELECT, INSERT ON shop.access_log TO loggerlocalhost;授权之后要执行FLUSH PRIVILEGES;不过这里我要多说一句在MySQL 5.7及8.0里用GRANT语句直接创建或修改权限后不需要FLUSH PRIVILEGES因为权限表是动态加载的。FLUSH PRIVILEGES只有在直接改mysql.user表不推荐或者需要强制刷新权限缓存时才需要。多执行这一句没坏处但别以为不执行权限就不生效。3.5 第四步验证建完一切一定要实际验证一遍别急着说配好了。先看用户和主机SELECT user, host, plugin FROM mysql.user WHERE user shop_app;再看用户的权限SHOW GRANTS FOR shop_app192.168.1.50;最后用这个新用户实际连一下mysql -u shop_app -p -h 数据库IP shop到这里建库、建用户、授权、验证完整闭环就达成了。这套流程我走了无数次真心建议每一步都执行一遍验证命令尤其是在给客户交付或者接手的项目里多一次的确认能让后续少一个深夜的告警电话。4. 权限管理建好之后还要会收4.1 改密与权限调整应用上线后最常见的需求是改密码ALTER USER shop_app192.168.1.50 IDENTIFIED BY 新密码;注意老版本写法是SET PASSWORD FOR shop_app192.168.1.50 PASSWORD(新密码)这条在8.0已经废弃了。现在统一用ALTER USER简洁且规范。需要缩小权限时用REVOKE。比如某天发现报表账号权限给大了要收回DELETE权限REVOKE DELETE ON shop.* FROM report_user192.168.1.%;4.2 什么时候该单独建账号我常被问到一个项目建多少个账号合适。我的经验是最少三个账号用途权限应用主账号业务增删改查只授该库的表权限不给DDL只读账号报表、BI、数据分析SELECT管理账号结构变更、迁移DDL DML有人会问应用账号要不要给CREATE/ALTER我的建议是线上应用账号绝对不给DDL权限。数据库表结构变更应该走发布流程由专门的迁移账号在发布窗口执行而不是让应用自己ALTER TABLE。否则一旦代码里混入恶意SQL或者误操作连补救的机会都没有。这里也顺便说一下别把所有应用的库放在同一个MySQL实例里还共用一个root。多个库共用一个实例没问题资源隔离需求不大的话但账号必须分。4.3 删除没用的账号项目下线之后账号要及时清理DROP USER shop_app192.168.1.50;DROP USER会自动撤销该用户的所有权限不需要先REVOKE再DROP。这也是比先删权限表记录更干净的做法。还有一点容易被忽视如果同一个用户名同时存在localhost和%两条记录DROP USER时必须分别指定host不能只写用户名。很多人在这里掉坑删了一个以为全删了结果另一个还在测试连接居然还能通让人一度以为见鬼了。5. 高频报错和排查实录被坑出来的实战经验这部分是我最想写的。因为建库建用户本身不难但报错能把人气死。我把这几年遇到的高频问题整理成一张速查表报错信息含义排查方向ERROR 1045 (28000): Access denied for user...认证失败密码是否正确、用户是否存在、host是否匹配ERROR 1130: Host is not allowed to connect来源IP不在允许范围host字段是否包含你的来源IPERROR 1396: Operation CREATE USER failed用户已存在或残留记录检查mysql.user是否有同名记录ERROR 1410: You are not allowed to create a user with GRANT当前账号无授权权限使用高权限账号操作Authentication plugin caching_sha2_password cannot be loaded客户端驱动不支持8.0新插件升级驱动或给该用户指定老插件Unknown database库不存在或未授权检查库名、授权是否包含该库5.1 Access denied 的三种可能性报1045时90%的人第一反应是密码写错了。但其实还有两个可能用户名对应的host没匹配上或者用户压根不存在。MySQL匹配用户是按user host联合匹配的而且host有优先级规则。如果你创建的是shop_app192.168.1.50但应用服务器换了IP变成192.168.1.51连接的时候MySQL找不到完全匹配的host就会报Access denied。这时候你用shop_app%登录反而能成功。所以排查1045一定要先看mysql.user表里实际有哪些记录SELECT user, host FROM mysql.user WHERE user shop_app;5.2 host不匹配和通配符的坑如果你执行下面的语句CREATE USER demo% IDENTIFIED BY 123456;再用demo从本地连一定会报1045。因为MySQL里localhost和%是不同匹配路径。具体来说MySQL的匹配顺序是先找最精确的匹配里面host优先级有明确规则具体值优先于通配符。也就是说demolocalhost会优先于demo%。如果你只创建了demo%没有创建demolocalhost本地连接时系统会尝试匹配匿名用户或者直接拒绝。实际工作中最稳妥的做法是对于本机要用的账号同时创建localhost和实际来源IP两个条目或者干脆只创建localhost。对于远程连接精确到IP。永远不要图省事只给%。5.3 8.0驱动的认证插件兼容这个问题在2020年前后特别多。很多用老项目的同学升级到MySQL 8.0后应用一下连不上报的就是认证插件错误。解决方案有三个升级客户端驱动支持caching_sha2_password建用户时显式指定老的mysql_native_password插件修改已有用户的认证插件ALTER USER shop_app192.168.1.50 IDENTIFIED WITH mysql_native_password BY 新密码;方案2和3是临时止血长期还得升级驱动。另外如果用的是Navicat等图形界面旧版本也要升级不然也会遇到同样的报错。5.4 改了密码但应用还是连不上有一种情况特别隐蔽ALTER USER改完密码之后应用侧用的是连接池池里的旧连接没有断开导致一直用旧密码重连或者保持旧连接。表面上看像改密码没生效其实是你应用有连接池缓存重启应用或者主动清空连接池就好了。我在一次线上变更时就踩过这个坑数据库密码按规范改完业务方说连接失败我本地拿新密码一登秒成功最后发现是对方服务的长连接没释放。很多MySQL问题其实不是MySQL的问题是连接管理的问题。还有一个更隐蔽的有些框架会把SQLAlchemy或JDBC连接串里的密码做URL编码如果新密码里有特殊字符比如、#、%没有转义就直接报连不上。排查时把连接串打印出来看看密码是否被截断或错误解析了。5.5 建库报Access denied但root能登这种情况多半是你登录时用了某个DML权限的账号而不是真正的管理账号。可以先用SELECT CURRENT_USER();看当前身份是谁再SHOW GRANTS;看有没有CREATE、CREATE USER之类的权限。没有就换账号操作别硬刚。6. 从零到一一份可以直接照抄的落地清单最后我把这套流程浓缩成一份可以直接用的清单方便你接到新项目时对照执行明确应用连接数据库的方式本机还是远程来源IP是什么用高权限账号登录MySQL创建数据库显式指定utf8mb4字符集和排序规则创建专用用户host精确到来源IP或网段按最小权限原则授权只授业务需要的操作用SHOW GRANTS确认授权结果用新账号实际连接并执行一条最简单的查询验证把账号密码、用途、创建日期记录到团队密码管理平台设置定期密码轮换机制如果有条件按照这份清单我基本上可以在五分钟内完成一个干净、安全、可维护的数据库账号配置。你也可以根据自己的团队规范调整但最小权限和单独账号这两条底线不要动。我个人在实际操作中最大的体会是建库建用户这件事看起来基础但恰恰是基础环节最能体现工程素养。很多线上事故追根溯源都是权限边界模糊。多花五分钟把账号拆清楚比你事后花五个小时去查谁动了这个表要值太多。如果这个项目后续还要加迁移脚本、加只读通道根基一定是今天这一条命令打实的。建议你现在就打开终端把上面的命令亲手跑一遍——建一个测试库、建一个专用账号、授一个最小权限整个过程十分钟不到但之后再遇到相关报错你心里就有底了。