MySQL 避坑指南系列·第①篇,共 4 篇。
本系列从安装配置、Docker 部署、SQL 性能到生产运维,覆盖 MySQL 全链路的高频踩坑。建议收藏整个系列,按需查阅。
很多人装完 MySQL 就直接开干,等到中文乱码、连不上远程、配置没生效这些问题出现,才意识到「安装」这步其实坑不少。本篇整理安装、字符集、配置文件、用户权限 4 个方向共 10 个高频坑,每个坑都有根因和解决方案。
环境说明
| 项目 |
版本 |
| MySQL |
5.7.x / 8.0.x(差异处单独标注) |
| 操作系统 |
Ubuntu 20.04/22.04、CentOS 7/8/9 |
一、安装坑(3 个)
坑 1:系统源安装的 MySQL 版本太旧
现象:
sudo apt install mysql-server
mysql --version
# mysql Ver 8.0.28,但项目要求 8.0.36
系统默认源的 MySQL 版本由发行版维护,更新严重滞后于 MySQL 官方,小版本差异有时包含重要安全补丁。
根本原因: Linux 发行版打包更新周期慢,apt/yum 默认源不是 MySQL 官方维护的。
解决方案: 使用 MySQL 官方源安装指定版本。
# Ubuntu:添加官方 APT 源
wget https://dev.mysql.com/get/mysql-apt-config_0.8.30-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.30-1_all.deb
# 弹窗中选择目标版本(如 MySQL 8.0)后确认
sudo apt update && sudo apt install mysql-server
# CentOS:添加官方 YUM 源
sudo rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-11.noarch.rpm
sudo yum install mysql-community-server
坑 2:装完不知道 root 密码在哪
现象: 安装完成后执行 mysql -uroot -p,输什么密码都报 Access denied。
根本原因: MySQL 8.0 安装时会把临时密码写到日志;Ubuntu 系的系统还可能用 auth_socket 插件认证,根本不需要密码,直接用 sudo 进。
解决方案:
# 方法一:从日志找临时密码(MySQL 8.0 通用)
sudo grep 'temporary password' /var/log/mysql/error.log
# 方法二:auth_socket 模式,直接用 sudo 进入(不加 -p)
sudo mysql
# 进入后立刻修改 root 密码
ALTER USER 'root'@'localhost'
IDENTIFIED WITH mysql_native_password BY 'YourPassword123!';
FLUSH PRIVILEGES;
坑 3:mysql_secure_installation 跑完反而登不进去
现象: 安全初始化时密码明明设了,重新登录还是 Access denied。
根本原因: MySQL 8.0 默认认证插件是 caching_sha2_password,部分客户端和驱动不兼容;也有可能是 socket 认证和密码认证混用,互相覆盖。
解决方案:
-- 检查 root 当前用的认证插件
SELECT user, host, plugin FROM mysql.user WHERE user='root';
-- 如果插件是 auth_socket 或 caching_sha2_password,改成兼容性更好的
ALTER USER 'root'@'localhost'
IDENTIFIED WITH mysql_native_password BY 'YourPassword123!';
FLUSH PRIVILEGES;
二、字符集与编码坑(2 个)
坑 4:中文写进去,查出来是问号 ???
现象: INSERT 中文成功,SELECT 出来全是 ??? 或乱码。
根本原因: MySQL 5.7 默认字符集是 latin1,根本无法存储中文。即使数据库字符集对了,连接字符集不对照样乱码——数据库、表、连接三者必须统一。
解决方案:
第一步,定位问题范围:
SHOW VARIABLES LIKE 'character%';
SHOW VARIABLES LIKE 'collation%';
第二步,修改配置文件(/etc/mysql/mysql.conf.d/mysqld.cnf 或 /etc/my.cnf):
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
第三步,已有库和表的字符集迁移:
-- 修改数据库
ALTER DATABASE your_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- 修改表(含所有列)
ALTER TABLE your_table
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
⚠️ 一定要用 utf8mb4,不是 utf8。 MySQL 里的 utf8 是残缺版,只支持最多 3 字节的字符,emoji(4 字节)存不进去,会直接截断或报错。
坑 5:查询不区分大小写,排序结果乱
现象: WHERE name = 'Alice' 同时查出 alice、ALICE;ORDER BY 排序不符合预期。
根本原因: utf8mb4_general_ci 和 utf8mb4_unicode_ci 都是大小写不敏感排序规则(ci = case insensitive)。
解决方案:
-- 需要精确区分大小写时,列级别改用 _bin 规则
ALTER TABLE users
MODIFY name VARCHAR(100)
CHARACTER SET utf8mb4
COLLATE utf8mb4_bin;
-- 或者只在查询时临时指定,不改表结构
SELECT * FROM users
WHERE name COLLATE utf8mb4_bin = 'Alice';
三、配置文件坑(3 个)
坑 6:明明改了 my.cnf,重启后配置没变化
现象: 修改了 /etc/my.cnf 并重启 MySQL,SHOW VARIABLES 里的值还是原来的。
根本原因: MySQL 按固定顺序读取多个配置文件,后读的覆盖先读的。改了一个文件,可能有另一个文件把它覆盖了。
解决方案: 先确认 MySQL 实际读的是哪个文件:
mysql --verbose --help | grep -A 1 'Default options'
# 典型输出示例:
# /etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf
然后把自定义配置统一写到优先级最高的那个文件。Ubuntu 建议写到 /etc/mysql/mysql.conf.d/mysqld.cnf,CentOS 建议写到 /etc/my.cnf。
坑 7:Too many connections,连 root 都进不去
现象: 高峰期报 ERROR 1040: Too many connections,DBA 想进去排查,发现 root 账号也连不上。
根本原因: max_connections 默认 151,所有连接额度耗尽后,任何账号(包括 root)都无法新建连接。
解决方案:
[mysqld]
# 调整总连接上限
max_connections = 500
# 为超级用户保留连接额度,管理员永远能进去
max_user_connections = 490
# 不重启临时生效
SET GLOBAL max_connections = 500;
# 查看当前连接数和连接来源
SHOW STATUS LIKE 'Threads_connected';
SHOW PROCESSLIST;
调大连接数不是根本解法。每个连接约占 1MB 内存,500 个就是 500MB。真正的解法是连接池:Java 用 HikariCP,Python 用 SQLAlchemy 连接池,Node.js 用 mysql2 pool,连接池能让几十个真实连接服务几百个并发请求。
坑 8:lower_case_table_names 迁移时表突然找不到
现象: 从 Linux 迁移到 Windows(或容器迁移),某些表查询报 Table doesn't exist,但明明存在。
根本原因: Linux 文件系统区分大小写,Orders 和 orders 是两张不同的表;Windows 不区分,迁移后大小写不一致的表名发生冲突或丢失。MySQL 8.0 不允许初始化完成后再修改此参数,必须在初始化前设定。
解决方案:
# 必须在 MySQL 首次初始化(数据目录为空)之前写入
[mysqld]
# 0 = 区分大小写(Linux 默认)
# 1 = 不区分,存储时转为小写(跨平台推荐)
# 2 = 不区分,存储时保留原始大小写
lower_case_table_names = 1
治本之道:从项目开始就约定表名全部小写+下划线,彻底与此参数解耦。
四、用户权限坑(2 个)
坑 9:本地能连,远程客户端连不上
现象: 本机 mysql -uroot -p 正常,Navicat / DBeaver 远程连接报 Access denied for user 'root'@'xxx.xxx.xxx.xxx'。
根本原因: MySQL 默认 root 账号只允许 localhost 连接;另外 bind-address 默认绑定 127.0.0.1,根本不监听外部请求。
解决方案(开发环境):
第一步,修改 my.cnf 开启监听:
[mysqld]
# 监听所有网卡,不仅仅是本地
bind-address = 0.0.0.0
第二步,创建允许远程连接的专用账号(不要直接开放 root):
CREATE USER 'dev_user'@'%'
IDENTIFIED WITH mysql_native_password BY 'StrongPassword!';
GRANT ALL PRIVILEGES ON your_db.* TO 'dev_user'@'%';
FLUSH PRIVILEGES;
⚠️ 生产环境严禁对公网开放 3306 端口。 生产远程管理统一用 SSH 隧道:
ssh -L 3307:localhost:3306 user@your-server
# 然后本地连 127.0.0.1:3307 即可
坑 10:GRANT 执行成功,但用户还是没权限
现象: GRANT 语句没有报错,但用新账号连上去执行操作还是 Access denied。
根本原因: 2 个常见原因:① 忘记 FLUSH PRIVILEGES;② mysql.user 表里同一个用户名存在多条 host 不同的记录,MySQL 用的是最精确匹配的那条,而不是你刚改的那条。
解决方案:
-- 先查清楚用户的所有记录
SELECT user, host, plugin FROM mysql.user WHERE user='your_user';
-- 检查实际生效的权限
SHOW GRANTS FOR 'your_user'@'%';
-- 刷新权限缓存
FLUSH PRIVILEGES;
快速自检清单(安装配置)
完成安装后,逐项确认:
- ☑ 字符集已设为
utf8mb4,而不是 utf8 或 latin1
- ☑ 已确认 MySQL 实际读取的配置文件路径
- ☑
lower_case_table_names 在初始化前已按需设置
- ☑
max_connections 已根据预估并发量调整
- ☑ 已删除匿名用户:
DELETE FROM mysql.user WHERE User='';
- ☑ 已删除测试库:
DROP DATABASE IF EXISTS test;
- ☑ 已创建应用专用账号,未直接使用 root
- ☑ 3306 端口未对公网开放
小结
本篇 10 个坑的核心规律只有一条:MySQL 的默认配置不是为生产准备的,它追求的是兼容性和最小化,而不是安全性和性能。安装完之后,至少要把字符集、连接数、账号权限这 3 件事做对,后面才不会反复踩坑。
下一篇聚焦 Docker 部署,那是踩坑最密集的场景。欢迎到云栈社区继续探讨更多数据库实战经验。