RSS
菜单
全部文章快讯开发科技深度热点

第 41 章 安装与配置踩坑

内容摘要

本章汇总 MySQL 从"装得上"到"配得对"阶段最容易踩的 11 个坑,覆盖初始化、配置文件、权限、大小写、磁盘、端口、字符集、连接数与 binlog。很多线上事故不是运行后才发生,而是安装第一天就埋好了雷。本章所有示例基于 MySQL 8.0(Linux 通用二进制/源码包安装场景),读完你能避开 80% 的"新装即翻车"问题。

📖 本篇简介:本章汇总 MySQL 从"装得上"到"配得对"阶段最容易踩的 11 个坑,覆盖初始化、配置文件、权限、大小写、磁盘、端口、字符集、连接数与 binlog。很多线上事故不是运行后才发生,而是安装第一天就埋好了雷。本章所有示例基于 MySQL 8.0(Linux 通用二进制/源码包安装场景),读完你能避开 80% 的"新装即翻车"问题。

目录

  • 坑 1:安装后忘记初始化数据目录就直接启动
  • 坑 2:改了配置文件却不生效
  • 坑 3:skip-grant-tables 排障后忘记关闭
  • 坑 4:root 无法远程登录
  • 坑 5:表名大小写参数初始化后再改
  • 坑 6:数据目录放在系统盘被写满
  • 坑 7:端口被占或找不到 socket 文件
  • 坑 8:初始化默认字符集是 latin1
  • 坑 9:连接数上限拍脑袋设置
  • 坑 10:binlog 没开或把磁盘写爆
  • 坑 11:my.cnf 写错参数名导致启动失败
  • 速查表:安装配置高频坑速记
  • 自测:你能避坑吗

坑 1:安装后忘记初始化数据目录就直接启动

场景 / 错误示范:

# 下载解压 MySQL 8.0 通用二进制包后,新手直接启动服务
shell> tar -zxvf mysql-8.0.36-linux-glibc2.17-x86_64.tar.gz
shell> cd mysql-8.0.36-linux-glibc2.17-x86_64
shell> bin/mysqld_safe --user=mysql &
# 结果:报错 "Do you have the privilege to write to /usr/local/mysql/data"
# 或者:data 目录是空的,根本不存在 mysql 系统库

为什么是坑:MySQL 8.0 的数据目录不会随解压自动生成,必须手动执行 mysqld --initialize 初始化出 mysql、sys 等系统库和权限表。跳过这一步直接启动,服务要么起不来,要么起来后连账号体系都没有。很多人按网上 5.x 老教程操作,5.x 的 mysql_install_db 在 8.0 里已经废弃,照抄必踩。

正确做法:

# 1) 建独立的数据目录并授权给 mysql 用户
shell> mkdir -p /data/mysql && chown -R mysql:mysql /data/mysql

# 2) 初始化(--initialize 会生成一个一次性 root 临时密码,打印在 error log 里)
shell> bin/mysqld --initialize --user=mysql --datadir=/data/mysql
# 在日志末尾可以看到:A temporary password is generated for root@localhost: xxxxx

# 3) 启动服务
shell> bin/mysqld_safe --user=mysql --datadir=/data/mysql &

# 4) 用临时密码登录后,第一件事就是改密码
shell> bin/mysql -uroot -p'xxxxx'
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'StrongPass@2026';

加分点:--initialize-insecure 会生成空密码,仅适合本地测试,生产绝不用。初始化时的 --datadir 必须指向一个空目录,若里面已有数据会直接拒绝启动。

坑 2:改了配置文件却不生效

场景 / 错误示范:

# 程序员把配置写进了 /etc/my.cnf,重启后发现参数纹丝不动
[mysqld]
innodb_buffer_pool_size = 4G
-- 登录后查看,还是默认值
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
-- 结果只有 134217728(128M),说明配置根本没被读到

为什么是坑:MySQL 启动时按一组固定顺序搜索配置文件,若你的 my.cnf 不在搜索路径里,写得再对也白搭。常见错误:把文件写成 my.cnf.txt、放在 home 目录、或编译时指定的 --sysconfdir 与你以为的路径不一致。还有一种情况是文件里有语法错误,MySQL 静默跳过后面的段落。

正确做法:

# 第一步:用 mysqld --help --verbose 看它到底从哪读配置(前几行就是默认顺序)
shell> bin/mysqld --verbose --help | grep -A1 "Default options"
# Default options are read from the following files in the given order:
# /etc/my.cnf /etc/mysql/my.cnf /usr/local/mysql/etc/my.cnf ~/.my.cnf

# 第二步:把配置写到它真正读的那个路径,例如 /etc/my.cnf
shell> cat /etc/my.cnf
[mysqld]
datadir=/data/mysql
socket=/data/mysql/mysql.sock
innodb_buffer_pool_size=4G

# 第三步:改完必须重启(动态参数除外),并用命令验证
shell> systemctl restart mysqld
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';   -- 确认生效

加分点:my_print_defaults mysqld 可以打印 MySQL 实际解析到的参数,排障"配置不生效"比翻日志更快。记住:静态参数必须重启,动态参数才需要 SET GLOBAL。

坑 3:skip-grant-tables 排障后忘记关闭

场景 / 错误示范:

# 忘记 root 密码,临时在配置里加了这行绕过权限
[mysqld]
skip-grant-tables
shell> systemctl restart mysqld
shell> mysql -uroot          # 不用密码直接进
mysql> FLUSH PRIVILEGES;
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'newpwd';
# 改完密码后——忘记把 skip-grant-tables 从配置里删掉,也忘记重启

为什么是坑:skip-grant-tables 会完全关闭权限校验,任何人不用密码就能连上来做任意操作,包括 DROP DATABASE。更危险的是它默认还会关掉 binlog 写权限变更相关功能,排障完不关等于把数据库裸奔在网口上。很多人排障完急着去忙别的,这行配置就留了几个月。

正确做法:

# 正确姿势:排障时临时在命令行加参数,而不是写进配置文件
# shell> mysqld_safe --skip-grant-tables --skip-networking &
#                    ^^^^^^^^^^^^^^^^^^^^ 同时禁用网络,只允许本机

# 改完密码后,立刻:
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass@2026';
mysql> FLUSH PRIVILEGES;
mysql> \q

# 然后正常重启(不带任何跳过参数)
shell> systemctl restart mysqld

# 最后验证:现在必须密码才能登录
shell> mysql -uroot
# ERROR 1045 (28000): Access denied——说明权限校验已恢复

加分点:排障时务必同时加 --skip-networking,让跳过权限的实例只监听本地 socket,外网连不进来,双保险。8.0 还可以用 --init-file 在启动时执行改密码 SQL,比手改配置更干净。

坑 4:root 无法远程登录

场景 / 错误示范:

-- 应用服务器连接数据库时报错
-- ERROR 1045 (28000): Access denied for user 'root'@'10.0.0.8' (using password: YES)
-- 新手第一反应是密码错了,反复重置密码,其实根本不是密码问题
mysql> SELECT user, host FROM mysql.user;
+------+-----------+
| user | host      |
+------+-----------+
| root | localhost |   -- root 只允许本机!
+------+-----------+

为什么是坑:MySQL 的账号是 user@host 二元组,root@localhost 只匹配从本机连进来的连接。从应用服务器(另一台机器)连时,匹配不到这条记录,于是报 Access denied。密码对不对根本不在判断范围内。直接把 root 开放成 root@'%' 又是安全大忌。

正确做法:

-- 不要给 root 开远程!应该新建专用业务账号,并限定来源网段
CREATE USER 'app_user'@'10.0.0.%' IDENTIFIED BY 'AppPass@2026';

-- 只授予该业务库的必要权限,而不是 ALL PRIVILEGES 全局大权
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'app_user'@'10.0.0.%';

-- 立刻生效
FLUSH PRIVILEGES;

-- 验证账号确实存在于正确的 host 段
SELECT user, host FROM mysql.user WHERE user = 'app_user';

加分点:除了 user 表里的 host,还要看 bind-address 是否绑了 127.0.0.1(那样外网根本连不上)、防火墙/安全组是否放行 3306。报错码 1045 是权限层问题,不是网络层问题,别去 telnet 端口瞎排查。

坑 5:表名大小写参数初始化后再改

场景 / 错误示范:

-- 在 Linux 上默认 lower_case_table_names=0(表名区分大小写)
-- 开发同学抱怨表名大小写不一致导致程序报错,于是想当然改配置
mysql> SHOW VARIABLES LIKE 'lower_case_table_names';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| lower_case_table_names | 0     |
+------------------------+-------+
# 直接在 my.cnf 里改成 1 然后重启
[mysqld]
lower_case_table_names=1

为什么是坑:这个参数是初始化时决定的,它会在初始化数据目录时把表名按规则落盘。初始化完成后再改,磁盘上已经按旧规则存好的表名与新规则不一致,轻则表"消失",重则 InnoDB 数据字典损坏、直接起不来。MySQL 8.0 甚至会在检测到不一致时拒绝启动。这是个典型的"以为是动态参数,其实是一次性初始化参数"的坑。

正确做法:

# 正确顺序:先决定要不要不区分大小写,再初始化数据目录
# 1) 数据目录为空时,先写好配置
shell> cat /etc/my.cnf
[mysqld]
lower_case_table_names=1   # 1 = 存表名转小写,比较时不区分大小写

# 2) 再初始化
shell> bin/mysqld --initialize --user=mysql --datadir=/data/mysql

# 3) 如果线上已经跑起来、确实要改,唯一安全做法是:
#     mysqldump 全量导出 -> 删数据目录 -> 改配置 -> 重新初始化 -> 导入
#    绝不能直接改完配置重启!

加分点:跨平台部署时建议统一设为 1(Windows 默认就是 1,macOS 默认 2),避免同一套 SQL 在 Linux 上区分大小写、在 Windows 上不区分,导致测试和生产行为不一致。

坑 6:数据目录放在系统盘被写满

场景 / 错误示范:

# 图省事,用默认路径,数据全写在系统盘
shell> bin/mysqld --initialize --datadir=/usr/local/mysql/data
# 跑了半年,系统盘 50G 被 ibdata、binlog、ibtmp1 吃光
shell> df -h /
# Use% 100%!MySQL 开始报错:The table is full / No space left on device

为什么是坑:系统盘通常容量小、还要承载操作系统日志和 swap,把数据库数据目录放这里,一旦写满,MySQL 会在写入时直接报错甚至 crash,恢复还要清理系统盘。更糟的是 ibdata1 自动膨胀、临时表 ibtmp1 暴涨、binlog 不断追加,这几个大户都默认在数据目录里。

正确做法:

# 把数据目录独立挂载到大盘,并把容易膨胀的东西也指过去
[mysqld]
datadir=/data/mysql                 # 独立数据盘
socket=/data/mysql/mysql.sock
tmpdir=/data/mysql/tmp              # 临时表/排序文件也放数据盘
innodb_temp_data_file_path=/data/mysql/ibtmp1:12M:autoextend
log_bin=/data/mysql/binlog/mysql-bin   # binlog 也独立规划空间
# 日常监控磁盘水位,预留至少 20% 余量
shell> df -h /data
# 同时监控 ibtmp1 大小:它在异常大查询时会疯狂膨胀

加分点:生产机应把数据盘、binlog 盘、备份盘分开挂载,避免某一路 IO 或容量把别的拖垮。部署第一天就 df 规划,比事后救火划算得多。

坑 7:端口被占或找不到 socket 文件

场景 / 错误示范:

# 启动报错:Can't start server: Bind on TCP/IP port: Address already in use
shell> systemctl start mysqld
# 或者登录报错:Can't connect to local MySQL server through socket '/tmp/mysql.sock'
shell> mysql -uroot -p

为什么是坑:3306 被另一个进程(比如上次没退干净的 mysqld、或另一个数据库)占用,绑定就失败。而 socket 文件路径对不上则更隐蔽:服务端写在 /data/mysql/mysql.sock,客户端却默认去 /tmp/mysql.sock 找,于是报"连不上",但用 mysql -h127.0.0.1 又能连——很多人被这个假象误导半天。

正确做法:

# 1) 查端口占用,把占坑的旧进程清掉
shell> netstat -tlnp | grep 3306
shell> ps -ef | grep mysqld
shell> kill -9 <残留进程号>

# 2) 让客户端和服务端用同一个 socket 路径
# 服务端 my.cnf:
[mysqld]
socket=/data/mysql/mysql.sock
# 客户端 my.cnf(同一个文件里再写一段):
[client]
socket=/data/mysql/mysql.sock

# 3) 验证
shell> mysql -uroot -p            # 走 socket
shell> mysql -uroot -p -h127.0.0.1   # 走 TCP,都应能连

加分点:连不上时先分清是 socket 问题(报错里有 .sock)还是 TCP 问题(报错里有 111 Connection refused)。前者对齐路径,后者查端口和 bind-address。

坑 8:初始化默认字符集是 latin1

场景 / 错误示范:

-- 装完没配字符集,直接建库建表,插入中文后变乱码
mysql> CREATE DATABASE appdb;
mysql> CREATE TABLE t(name VARCHAR(50));
mysql> INSERT INTO t VALUES('张三');
-- Query OK,但 SELECT 出来是 '???' 或一堆方块
mysql> SHOW VARIABLES LIKE 'character_set_server';
+----------------------+---------+
| character_set_server | latin1  |   -- 8.0 之前默认是 latin1!
+----------------------+---------+

为什么是坑:MySQL 5.7 及更早版本服务端默认字符集是 latin1,一个中文字符要存 3 个字节,latin1 单字节装不下,入库就被截断成 ?。这种乱码是存进去就坏了,事后怎么改连接字符集都救不回来。8.0 虽然默认改成 utf8mb4,但如果你用的是 5.7 或自定义编译,这个坑依然存在。

正确做法:

# 初始化时就把字符集和排序规则写死,一劳永逸
[mysqld]
character_set_server=utf8mb4
collation_server=utf8mb4_0900_ai_ci

# 建库时显式指定,别依赖默认
CREATE DATABASE appdb
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

-- 建表也显式指定,防止继承到错误的库默认值
CREATE TABLE t (
  name VARCHAR(50)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO t VALUES('张三');   -- 这次正常存

加分点:字符集要在"服务端、库、表、连接"四层全部对齐,任何一层是 latin1 都可能引入乱码。详细排查见第 43 章。

坑 9:连接数上限拍脑袋设置

场景 / 错误示范:

# 为了"能扛并发",一把设到 10000
[mysqld]
max_connections=10000
-- 结果:连上是连上了,但内存飙升、性能反而更差
mysql> SHOW STATUS LIKE 'Threads_connected';
-- 实际同时活跃连接才 80 个,剩下都是连接池没释放的空连接

为什么是坑:每个连接在 MySQL 里都要分配线程栈、连接缓冲区、排序/join 缓冲。max_connections 设过大,等于允许数据库预占(或允许外部打满)巨大内存,单线程栈就几 MB,上万连接轻松吃掉几 GB,反而触发 swap 拖垮整体。真正的瓶颈不是"能开多少连接",而是"同一时刻能真正服务多少查询"。

正确做法:

# 按机器内存和业务量设一个合理上限,比如 500~1000
[mysqld]
max_connections=1000

# 关键配套:让连接池(应用侧)控制并发,而不是无限开连接
# 监控最大实际用到的连接数,再决定要不要调大
-- 查历史上同时达到过的最大连接数
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
-- 如果 Max_used_connections 长期远低于 max_connections,说明设大了

加分点:连接数不够时先看 Aborted_connects 和慢查询,多数"连接打满"其实是慢查询把连接占住不释放,正确做法是优化 SQL 而不是盲目调大 max_connections。连接池配置见第 67 章。

坑 10:binlog 没开或把磁盘写爆

场景 / 错误示范:

-- 情况一:从库/数据恢复都依赖 binlog,但线上居然没开
mysql> SHOW VARIABLES LIKE 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | OFF   |
+---------------+-------+
# 情况二:开了 binlog 却不配过期策略,一路写到磁盘满
[mysqld]
log_bin=/data/mysql/binlog/mysql-bin
# 忘了配 expire_logs_days / binlog_expire_logs_seconds

为什么是坑:binlog 是主从复制和时间点恢复(PITR)的根基,生产库不开 binlog,等于出事时无法做基于日志的恢复,也搭不了从库。反过来,开了又不设保留期,binlog 无限追加,大促一天能产生几十上百 GB,写满数据盘后 MySQL 直接停写。这是两个方向相反、但同样致命的坑。

正确做法:

# 正确:开 binlog + 设定过期 + 限制单文件大小
[mysqld]
log_bin=/data/mysql/binlog/mysql-bin
binlog_expire_logs_seconds=604800     # 保留 7 天(8.0 推荐用这个,替代过期的 expire_logs_days)
max_binlog_size=1G                     # 单个 binlog 切到 1G
binlog_format=ROW                      # 生产推荐 ROW,主从一致性最好
-- 手动清理已备份、且不再被从库需要的 binlog
PURGE BINARY LOGS BEFORE '2026-10-01 00:00:00';

-- 查看当前 binlog 列表和占用
SHOW BINARY LOGS;

加分点:清理前先用 SHOW REPLICA STATUS\G 确认所有从库已经消费完要删的 binlog,否则会把从库还需要的日志删掉导致复制中断。

坑 11:my.cnf 写错参数名导致启动失败

场景 / 错误示范:

# 抄网上配置,把参数名写错了一个字母
[mysqld]
innodb_buffer_pool_size = 4G
max_connetions = 1000        -- 拼错:connections 写成 connetions
shell> systemctl start mysqld
# 报错:unknown variable 'max_connetions=1000',MySQL 直接退出不启动

为什么是坑:MySQL 对配置文件里无法识别的变量默认是直接报错退出(不像很多程序会忽略未知项)。一个拼写错误就能让整个数据库起不来,CI 环境上尤其坑——本地能起,上线时配置多抄了一行,启动脚本却没把 stderr 打出来,表现就是"服务莫名起不来"。还有一种更隐蔽:参数写在了错误的组(如写进了 [client] 段却给 mysqld 用)。

正确做法:

# 1) 改完配置先做"语法+可识别性"检查,再重启
shell> bin/mysqld --verbose --help | grep -i buffer_pool
shell> bin/mysqld --defaults-file=/etc/my.cnf --validate-config
# 8.0.16+ 支持 --validate-config,专门校验配置能否被正确解析

# 2) 启动时把错误日志路径指出来,便于定位
shell> bin/mysqld --defaults-file=/etc/my.cnf --user=mysql --log-error=/data/mysql/error.log &

# 3) 确认参数名拼写,例如正确的是
#    max_connections(不是 max_connetions)
#    innodb_buffer_pool_size(带下划线)
-- 启动后登录逐个核对关键参数是否真的是你想要的值
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';

加分点:上线新配置前,先在测试机用 --defaults-file 启一遍验证;把"配置变更后先 --validate-config 再滚动重启"写进运维 checklist,能避开一大批"改配置改崩库"的事故。

速查表:安装配置高频坑速记

| 坑 | 典型现象 | 根因一句话 | 正确动作 |

|---|---|---|---|

| 未初始化 | 启动报 data 目录无权限/无系统库 | 8.0 不会自动建数据目录 | 先 mysqld --initialize |

| 配置不生效 | SHOW VARIABLES 还是默认值 | 文件不在搜索路径 | mysqld --help 看路径 |

| 跳过权限 | 不用密码就能登录 | skip-grant-tables 忘关 | 排障后立刻移除并重启 |

| root 远程拒登 | 1045 Access denied | root@localhost 只配了本机 | 建 app_user@网段,别开 root@% |

| 大小写 | 改完 lower_case 表消失 | 该参数初始化时定死 | 初始化前定好,否则导出重建 |

| 磁盘满 | No space left on device | data 放系统盘 | data/tmp/binlog 挂大盘 |

| 连不上 | socket 找不到 / 端口占用 | 路径不一致或端口被占 | 对齐 socket,清残留进程 |

| 中文乱码 | 入库变 ?? | 默认 latin1 | 初始化即 utf8mb4 |

| 连接数 | 内存飙升性能反降 | max_connections 拍太大 | 按内存设 500~1000 |

| binlog | 磁盘被写爆 / 无法恢复 | 没开或没设过期 | 开 binlog + 7 天过期 |

| 参数拼错 | 启动直接退出 | unknown variable | --validate-config 校验 |

自测:你能避坑吗

题 1:systemctl start mysqld 报 "Access denied for user 'root'@'10.0.0.8'",最可能的根因是什么?

<details>

<summary>参考答案</summary>

报错来自权限表的 host 匹配:root 只建了 root@localhost,从应用服务器 IP 连过来匹配不到。不要去重置密码,也不要图省事开 root@'%';应新建 业务账号@'应用网段%' 并按库授权。同时检查 bind-address 和防火墙。

</details>

题 2:为什么 lower_case_table_names=1 不能在数据目录初始化之后再改?

<details>

<summary>参考答案</summary>

该参数决定初始化时表名如何在数据字典里落盘。改了之后,磁盘上已按旧规则存的表名与新规则不一致,会导致 InnoDB 数据字典错乱甚至拒绝启动。正确做法是初始化前定好;线上必须改时,只能全量导出 → 删库 → 改配置 → 重新初始化 → 导入。

</details>

题 3:binlog 既不能不开、也不能无限保留,为什么?

<details>

<summary>参考答案</summary>

不开 binlog 就没有主从复制和时间点恢复能力,出事无法恢复;开了不设 binlog_expire_logs_seconds,日志无限追加会写爆数据盘导致停库。正确做法:开启 ROW 格式 binlog,保留 7 天左右,清理前确认从库已消费完毕。

</details>

题 4:改完 /etc/my.cnf 里的 innodb_buffer_pool_size,重启后 SHOW VARIABLES 却没变,怎么排查?

<details>

<summary>参考答案</summary>

先用 mysqld --verbose --help 确认它实际读取的配置文件顺序,看你的文件是否在其中;再用 my_print_defaults mysqld 看解析结果;检查是否写成了 my.cnf.txt 或拼错了参数名(8.0 可 --validate-config);最后确认是否真的重启成功。

</details>

下一篇预告:第 42 章 建表与数据类型踩坑

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 41 章 安装与配置踩坑》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。