☰
MySQL建库建用户与授权实操:权限模型、字符集与避坑指南
2026/10/13 10:57:41 网站建设 项目流程

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及更早版本默认是latin1,5.7之后默认是utf8mb4,但如果你用某些云厂商的默认配置,或者从老版本升级上来的实例,默认值可能依然是latin1或者utf8(注意是utf8,不是utf8mb4)。

这里必须说清楚一个历史坑:MySQL里的utf8其实是个"假utf8",它最多只能存3个字节,存不下emoji(4字节)以及一些生僻汉字。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_ci,5.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 'user'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码';

不过这只是过渡方案。caching_sha2_password 是官方默认,新项目尽量让驱动跟上来,别为了省事把安全等级拉低。这个问题我在后面"常见报错"里会再展开。

2.3 用户host字段:你以为是主机地址,其实是白名单

MySQL用户的完整写法是用户名@主机,比如'myapp'@'localhost'和'myapp'@'%'是不同的两个用户。这里的host不是"我自己的IP",而是允许从哪里来连接。

常见取值:

host值含义适用场景
localhost只允许本机连接脚本、本地管理工具、数据库与应用同机
127.0.0.1本机TCP连接与localhost走socket不同,需区分连接方式
%任何地址图省事但风险最高,不建议生产环境使用
192.168.1.100单个IP指定的应用服务器,最推荐
192.168.1.%特定网段一组内网IP,适合多台应用服务器

我记得有次排查一个"密码明明是对的但连不上"的问题,最后发现就是因为当时创建了'myapp'@'localhost',而应用服务器是通过内网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_app'@'192.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_app'@'192.168.1.50';

这条语句的意思是:允许这个用户在shop库下的所有表上执行所有操作(SELECT、INSERT、UPDATE、DELETE、CREATE、ALTER、DROP等)。但这里的ALL不包含GRANT OPTION,也就是说这个用户不能把权限再转授给别人,这正好是我们想要的——权限收敛。

如果只给只读权限(比如报表账号):

GRANT SELECT ON `shop`.* TO 'report_user'@'192.168.1.%';

如果只给某个表的权限(比如日志账号只写某张表):

GRANT SELECT, INSERT ON `shop`.`access_log` TO 'logger'@'localhost';

授权之后要执行:

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_app'@'192.168.1.50';

最后用这个新用户实际连一下:

mysql -u shop_app -p -h 数据库IP shop

到这里,建库、建用户、授权、验证,完整闭环就达成了。这套流程我走了无数次,真心建议每一步都执行一遍验证命令,尤其是在给客户交付或者接手的项目里,多一次的确认能让后续少一个深夜的告警电话。

4. 权限管理:建好之后还要会收

4.1 改密与权限调整

应用上线后最常见的需求是改密码:

ALTER USER 'shop_app'@'192.168.1.50' IDENTIFIED BY '新密码';

注意老版本写法是SET PASSWORD FOR 'shop_app'@'192.168.1.50' = PASSWORD('新密码'),这条在8.0已经废弃了。现在统一用ALTER USER,简洁且规范。

需要缩小权限时用REVOKE。比如某天发现报表账号权限给大了,要收回DELETE权限:

REVOKE DELETE ON `shop`.* FROM 'report_user'@'192.168.1.%';

4.2 什么时候该单独建账号

我常被问到"一个项目建多少个账号合适"。我的经验是最少三个:

账号用途权限
应用主账号业务增删改查只授该库的表权限,不给DDL
只读账号报表、BI、数据分析SELECT
管理账号结构变更、迁移DDL + DML

有人会问,应用账号要不要给CREATE/ALTER?我的建议是:线上应用账号绝对不给DDL权限。数据库表结构变更应该走发布流程,由专门的迁移账号在发布窗口执行,而不是让应用自己ALTER TABLE。否则一旦代码里混入恶意SQL或者误操作,连补救的机会都没有。

这里也顺便说一下,别把所有应用的库放在同一个MySQL实例里还共用一个root。多个库共用一个实例没问题(资源隔离需求不大的话),但账号必须分。

4.3 删除没用的账号

项目下线之后,账号要及时清理:

DROP USER 'shop_app'@'192.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字段是否包含你的来源IP
ERROR 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_app'@'192.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优先级有明确规则,具体值优先于通配符。也就是说'demo'@'localhost'会优先于'demo'@'%'。如果你只创建了'demo'@'%',没有创建'demo'@'localhost',本地连接时系统会尝试匹配匿名用户或者直接拒绝。

实际工作中,最稳妥的做法是:对于本机要用的账号,同时创建localhost和实际来源IP两个条目,或者干脆只创建localhost。对于远程连接,精确到IP。永远不要图省事只给%。

5.3 8.0驱动的认证插件兼容

这个问题在2020年前后特别多。很多用老项目的同学升级到MySQL 8.0后,应用一下连不上,报的就是认证插件错误。

解决方案有三个:

  1. 升级客户端驱动,支持caching_sha2_password;
  2. 建用户时显式指定老的mysql_native_password插件;
  3. 修改已有用户的认证插件:
ALTER USER 'shop_app'@'192.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. 从零到一:一份可以直接照抄的落地清单

最后,我把这套流程浓缩成一份可以直接用的清单,方便你接到新项目时对照执行:

  1. 明确应用连接数据库的方式:本机还是远程?来源IP是什么?
  2. 用高权限账号登录MySQL
  3. 创建数据库,显式指定utf8mb4字符集和排序规则
  4. 创建专用用户,host精确到来源IP或网段
  5. 按最小权限原则授权,只授业务需要的操作
  6. 用SHOW GRANTS确认授权结果
  7. 用新账号实际连接并执行一条最简单的查询验证
  8. 把账号密码、用途、创建日期记录到团队密码管理平台
  9. 设置定期密码轮换机制(如果有条件)

按照这份清单,我基本上可以在五分钟内完成一个干净、安全、可维护的数据库账号配置。你也可以根据自己的团队规范调整,但"最小权限"和"单独账号"这两条底线不要动。

我个人在实际操作中最大的体会是:建库建用户这件事看起来基础,但恰恰是基础环节最能体现工程素养。很多线上事故,追根溯源都是权限边界模糊。多花五分钟把账号拆清楚,比你事后花五个小时去查"谁动了这个表"要值太多。如果这个项目后续还要加迁移脚本、加只读通道,根基一定是今天这一条命令打实的。建议你现在就打开终端,把上面的命令亲手跑一遍——建一个测试库、建一个专用账号、授一个最小权限,整个过程十分钟不到,但之后再遇到相关报错,你心里就有底了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询