T5、常见问题
T5.1、mysql5.7 内存异常
社区讨论
mysql5.7 bug
containerd bug
mysql5.7 通过 docker v25.0.5 或 containerd v1.5.10 启动,会出现内存被异常占用 16G,导致操作系统 OOM 问题,docker v28.0 这个问题已解决。
社区虽然给出了解决方案,但是很少有人关注源码层面是由于什么场景导致的 bug,参考官方的 bug 报告bugs.mysql.com。
结合官方 bug 报告,ChatGPT 给出如下分析(仅供参考):
根本原因不是 MySQL 随机“吃内存”,而是 mysqld 在启动时把进程的 RLIMIT_NOFILE(ulimit -n)的“当前值”当作可用文件数返回/使用——如果该值异常大(不是 RLIM_INFINITY),就会被直接采纳并用于后续的内存/数组分配,导致巨量分配并耗尽宿主机内存。
1、mysqld 先计算需要的文件数(max_open_files),然后调用 my_set_max_open_files() 去设置/获取 OS 层的限制。
源码表达式:
max_open_files = max( wanted_files, max_connections*5, open_files_limit );
files = my_set_max_open_files(max_open_files);
这是 mysqld 的启动逻辑,计算想要的文件数然后交给 my_set_max_open_files 去尝试设置/获取实际可用值。
2、my_set_max_open_files / set_max_open_files 会读 getrlimit(RLIMIT_NOFILE) 并在某些条件下直接返回当前 rlimit.rlim_cur。
源码(mysys/my_file.c)逻辑的关键点是:
- 调用
getrlimit(RLIMIT_NOFILE, &rlimit),把rlimit.rlim_cur存在old_cur。 - 如果
rlimit.rlim_cur == RLIM_INFINITY则把它设为 max_file_limit(合理); - 如果
rlimit.rlim_cur >= max_file_limit,函数会直接 return rlimit.rlim_cur(也就是把当前很大的 rlimit 值当作最终值返回)。 - 返回的这个 files 值会影响 mysqld 的内部数据结构分配(比如 my_file_info / my_file_limit 等),因此如果 files 很大就会触发巨量 malloc/分配。
- mysys 的 my_set_max_open_files / my_file.c 内部维护 my_file_limit / my_file_info 等结构,启动时会基于 files 大小(返回值)做内存分配/扩容。若 files 是上面那个异常大的 rlimit 值,分配就会按这个数去做,进而可能申请数十 GB。
- 这是 bug 报告里直接指出的行为:set_max_open_files(max_file_limit) ... can return the current limit ... and later used in a malloc which can become huge。也有实测日志(启动时报 Out of memory / 需要几 GB)。bugs.mysql.com
- 这是一个已知的 bug(触发条件:OS 给出一个非常大的 but ≠ RLIM_INFINITY 的 rlimit),MySQL 在后续版本里修了(8.0.19/8.0.20 提到修复)。
MySQL 的 bug 报告里开发者说明:这个问题已在 8.0.19/8.0.20 中修复(修补方式是对返回值做上限/限制等处理,避免直接用超大 rlimit 导致 OOM)。bugs.mysql.com
在很多现代 Linux/distributions(或 systemd 的某些改动)下,进程启动时内核/父进程可能给进程一个非常大的 RLIMIT_NOFILE(比如 1073741816),因为 mysqld 的启动流程把“当前 rlimit”当成可用文件数并直接返回/使用,最终用于按文件数预分配/扩容某些内部数组(my_file_info / 相关缓存),因此会申请非常大的内存 —— 就会把宿主机内存吃光或触发 swap / OOM。这个正是 bug 报告中复现与分析的核心。
bugs.mysql.com
www.percona.com
解决方案:
社区给的解决办法(在 Docker 启动时加 --ulimit nofile=...)是合理且常用的临时/部署层解决方案;长期方案可以是升级到包含修补的 MySQL 版本(8.0.19+ 的修补)或者在宿主/服务管理层(systemd)显式把 LimitNOFILE 设置到合适值。
docker-compose.yml
version: "3.8"
services:
mysql:
image: mysql:5.7
container_name: mysql57
restart: always
environment:
MYSQL_ROOT_PASSWORD: StrongPassw0rd! # 建议使用安全密码
MYSQL_DATABASE: appdb # 可选:初始化数据库
MYSQL_USER: appuser # 可选:初始化用户
MYSQL_PASSWORD: AppUserPass123! # 可选:初始化用户密码
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql # 数据持久化
- ./my.cnf:/etc/mysql/conf.d/my.cnf:ro # 自定义 MySQL 配置
command:
--default-authentication-plugin=mysql_native_password
ulimits: # 关键!限制文件句柄,防止内存暴涨
nofile:
soft: 262144
hard: 262144
deploy:
resources:
limits:
cpus: "2.0" # 限制 CPU
memory: 4G # 限制最大内存
reservations:
memory: 1G
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 10s
retries: 3
volumes:
mysql_data:
my.cnf
[mysqld]
# 基本
user = mysql
port = 3306
bind-address = 0.0.0.0
sql_mode = STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
# InnoDB 性能
innodb_buffer_pool_size = 2G # 占总内存 50%-70%
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 1 # 事务安全
innodb_file_per_table = 1
innodb_flush_method = O_DIRECT
# 连接和缓存
max_connections = 300
table_open_cache = 4096
open_files_limit = 262144 # 与 ulimit 对齐
thread_cache_size = 100
query_cache_type = 0 # MySQL 5.7 默认关闭
tmp_table_size = 64M
max_heap_table_size = 64M
# 日志
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# 字符集
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[client]
default-character-set = utf8mb4
T5.2、etcd 健康问题
给 etcd 配置完数据目录和wal目录后,启动服务出现如下问题:
etcd crashes on startup when wal-dir is bind-mounted to host directory, fails with unlinkat /var/lib/etcd/wal: device or resource busy
社区也遇到了,https://github.com/etcd-io/etcd/issues/20218
根本原因是磁盘的挂载目录名和 etcd 服务配置的目录同名,而 etcd 进程会强制创建指定的目录,如果存在会先删除,但是这个目录又是个挂载点,所以会有 device or resource busy 的异常,如下:
# 专门给 etcd 配置的磁盘,避免数据争用,etcd 对磁盘延迟要求很高
[root@master-01 ~]# df -h | grep "/dev/vdc"
/dev/vdc2 50G 756M 50G 2% /etcd/etcd_wal
/dev/vdc1 50G 399M 50G 1% /etcd/etcd_data
# 设置不同的wal目录,可以避免磁盘io竞争,提高性能
ETCD_DATA_DIR: "/etcd/etcd_data"
ETCD_WAL_DIR: "/etcd/etcd_wal"
这么配置,etcd 会强制创建这两个目录,但是这两个目录又是挂载点,必然会出现这种问题。
可以在挂载点下指定一个子目录,就可以解决问题:
# 设置不同的wal目录,可以避免磁盘io竞争,提高性能
ETCD_DATA_DIR: "/etcd/etcd_data/data"
ETCD_WAL_DIR: "/etcd/etcd_wal/data"
T5.3、minio 生产上传失败
我们的生产环境,由于历史原因,上传文件到 minio 分成了两步,首先通过 getObjUrl() 连接 minio 拿到文件上传地址,传给另一个组件,然后通过 uploadFile(url) 上传文件。
这个过程经常遇到一些问题,拿不到上传地址,上传失败,找不到桶等,不想深入研究了,按下面步骤重来一遍就行。
1、进入 minio 控制台,Administrator -> Buckets -> Create Bucket
创建两个 bucket ebd-mdm、ebd-collect
2、给创建的 bucket 设置访问策略
点击创建的 bucket,然后点击 Summary -> Access Policy,选择 Custom,设置如下,注意,其中桶名(Resource 块中的 ebd-mdm)需根据实际情况修改为当前设置的桶名
{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"AWS": ["*"]},"Action": ["s3?GetObject"],"Resource": ["arn:aws:s3:::ebd-mdm/*"]}]}
两个桶都需要进行同样配置操作
3、创建普通上传账户
点击 Administrator -> Identity -> Users -> Createt User
输入账户名和密码,如 bigdata/Takweb@bigdata123
Policy 选择 readwrite
4、为账户创建 AccessKey
Administrator -> Identity -> Users,点击创建的用户 -> Service Accounts -> Create Access Key -> create,然后复制保存创建的 Access Key 和 Secret Key
5、设置 nacos
#上传地址,注意非控制台地址,两个端口不一致
MINIO_ENDPOINT=http://192.168.212.9:9000
#保存的Access Key
MINIO_AK=c6NqOka6NNG8ebhrRaGS
#保存的Secret Key
MINIO_SK=I0evYO4V4nuY2hLl3yYlo9vIX8WFAy7hbeUiyr8j
minio配置
#上传链接超时时间,单位分钟
MINIO_URL_EXPIRE=60
6、验证
先通过控制台上传一个测试图片,如测试图片 1.png
然后通过地址直接访问,如 http://192.168.212.9:9000/ebd-mdm/1.png
删除文件名,直接访问桶,若出现 Access Deny 则表名,桶权限配置成功,如果列出桶下所有的文件,则权限设置失败
T5.4、mysqldump 经 ProxySQL 导入报 ERROR 2013
T5.4.1、问题现象
- 从 MySQL 8.0.33(源) 导出,向 8.0.30(目标) 用
mysql < xxx.sql导入时,出现ERROR 2013 (HY000): Lost connection to MySQL server during query。 - 报错行号常落在 视图
class_view的CREATE VIEW前面,或 第一个触发器CREATE TRIGGER前面;容易误以为是视图 SQL 或缺表。 --skip-triggers --ignore-views --skip-routines整库导出再导入 可以成功;单独带上视图 或 单独带上触发器 会 2013。- 连接目标时,客户端欢迎语为
Server version: 8.0.30 (ProxySQL)(或曾出现ProxySQL Error: Access denied),说明流量经 ProxySQL,不是直连 mysqld。 - 复现后可能出现
ERROR 2003 (HY000): ... (111)(端口短暂连不上),与 代理崩溃重启 相符。
T5.4.2、复现问题
(1)与线上一致的导入(会失败)
# 示例:含视图(不 ignore-views)
mysql -h<目标> -uroot -p base_platform < step1_with_views.sql
# 示例:含触发器(不 skip-triggers)
mysql -h<目标> -uroot -p base_platform < step3_with_triggers_only.sql
(2)成功基线(对照:不执行视图/触发器/routine 段)
mysqldump -h<源> -uroot -p \
--skip-triggers --ignore-views --skip-routines \
--databases base_platform > structure_ok.sql
mysql -h<目标> -uroot -p base_platform < structure_ok.sql
(3)单句复现(经同一 ProxySQL 入口登录后执行)
SET character_set_client = utf8mb3; -- 一般可过
SET character_set_results = utf8mb3; -- 触发 ERROR 2013,随后可能出现 2003(111)
与 dump 中一致时,视图段为 /*!50001 SET character_set_results = utf8mb3 */,触发器段为 /*!50003 SET character_set_results = utf8mb3 */,效果相同:尚未执行 CREATE VIEW / CREATE TRIGGER 正文即可断连。
实际测试如下:
[root@distributor ~]# mysql -h 10.148.129.240 -u root -p --max_allowed_packet=268435456 base_platform
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.30 (ProxySQL)
Copyright (c) 2000, 2026, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
mysql> SET character_set_client=utf8mb3;
No connection. Trying to reconnect...
Connection id: 3
Current database: base_platform
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> SET character_set_client=utf8mb3;
Query OK, 0 rows affected (0.00 sec)
mysql> SET character_set_results=utf8mb3;
ERROR 2013 (HY000): Lost connection to MySQL server during query
No connection. Trying to reconnect...
ERROR 2003 (HY000): Can't connect to MySQL server on '10.148.129.240:3306' (111)
ERROR:
Can't connect to the server
mysql>
mysql> SET character_set_client=utf8mb3;
No connection. Trying to reconnect...
Connection id: 3
Current database: base_platform
Query OK, 0 rows affected (0.01 sec)
mysql>
mysql>
mysql>
mysql> /*!50001 SET character_set_client = utf8mb3 */;
Query OK, 0 rows affected (0.00 sec)
mysql> /*!50001 SET character_set_results = utf8mb3 */;
ERROR 2013 (HY000): Lost connection to MySQL server during query
No connection. Trying to reconnect...
ERROR 2003 (HY000): Can't connect to MySQL server on '10.148.129.240:3306' (111)
ERROR:
Can't connect to the server
mysql>
mysql> /*!50001 SET character_set_client = utf8mb3 */;
No connection. Trying to reconnect...
Connection id: 3
Current database: base_platform
Query OK, 0 rows affected (0.00 sec)
mysql> /*!50001 SET character_set_client = utf8mb3 */;
Query OK, 0 rows affected (0.00 sec)
mysql>
(4)说明
仅加 --routines 且 dump 内 CREATE PROCEDURE / CREATE FUNCTION 为 0 条 时导入成功,不能证明「存储过程一定无问题」,只能说明 本次文件没执行到同类带 utf8mb3 的视图/触发器包装段。
T5.4.3、问题分析和解决
T5.4.3.1、分析
- mysqldump 在 视图(
/*!50001)、触发器(/*!50003) 等块里会写SET character_set_client/character_set_results/collation_connection为utf8mb3,用于贴近对象定义时的连接字符集。 - MySQL 8.0 中
utf8mb3是合法字符集名(直连 mysqld 执行上述SET通常正常)。依据:MySQL 手册 — utf8mb3。 - 旧版 ProxySQL 对
SET character_set_results = utf8mb3处理有缺陷,可导致 断言失败 / 进程崩溃,客户端表现为 2013(查询过程中断连),与 MySQL 手册 — 2013 / CR_SERVER_LOST 及 B.3.2.7 MySQL server has gone away 中对「断连、未收齐应答」的描述一致。 - 与社区报告一致时可查
proxysql.log:Cannot find charset/collation [utf8mb3]、断言、signal 6、ProxySQL crashed. Restarting!- ProxySQL #4269
- 修复 PR:ProxySQL #4270(将
utf8mb3按utf8别名处理;2023-06-28 合并入v2.x)
- 官方写明带上该修复的发行版(2.x):ProxySQL 2.5.4(发布日 2023-07-19)Release notes 中列为 Major bug fix:「Fixed crashes when trying to set character set to
utf8mb3」#4269。即 PR #4270 对应问题在 2.5 系列里从 2.5.4 起有正式发行说明;2.5.3 与 issue 中复现版本同类,仍会触发崩溃。- 依据:GitHub — ProxySQL v2.5.4 Release notes
- 3.x:未在本文逐条核对每个 3.0.x 的 release note;实际升级请以
proxysql --version为准,并务必用SET character_set_results = utf8mb3;做冒烟(或走直连导入)。
T5.4.3.2、解决
| 做法 | 说明 |
|---|---|
| 升级 ProxySQL | 2.x:至少升级到 2.5.4(见上条官方 release);其它大版本以 SET character_set_results = utf8mb3 冒烟为准。 |
| 大导入直连 mysqld | 迁移窗口用 后端真实 IP、端口(非代理端口)执行 mysql < xxx.sql。 |
| 应急改 SQL | 将 utf8mb3 / utf8mb3_general_ci 批量改为 utf8mb4 / 对应 utf8mb4 collation` 后再经代理导入;必须在测试库验证,避免误伤。 |
| 导出参数 | mysqldump --default-character-set=utf8mb4 可作辅助,不保证去掉视图/触发器块内已写死的 utf8mb3,不能替代升级或直连。 |
T5.4.4、结论
- 根因:经 ProxySQL 时,
SET character_set_results = utf8mb3(及同段utf8mb3会话 SET) 触发 ProxySQL 已知缺陷,代理崩或断连 → 客户端 2013(及可能的 2003(111))。 - 表象:报错行「挨着」视图或触发器,实为 mysqldump 在这些对象前的字符集 SET;与视图 SELECT、触发器业务逻辑、基表是否存在无直接关系。
- 处置:优先 升级 ProxySQL 或 导入绕开代理;临时可对 dump 做
utf8mb3→utf8mb4类替换并充分验证。
