T5、常见问题

作者: Brinnatt 分类: 小工具 发布时间: 2025-08-11 16:42

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_viewCREATE 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、分析

  1. mysqldump视图(/*!50001触发器(/*!50003 等块里会写 SET character_set_client / character_set_results / collation_connectionutf8mb3,用于贴近对象定义时的连接字符集。
  2. MySQL 8.0utf8mb3 是合法字符集名(直连 mysqld 执行上述 SET 通常正常)。依据:MySQL 手册 — utf8mb3
  3. 旧版 ProxySQLSET character_set_results = utf8mb3 处理有缺陷,可导致 断言失败 / 进程崩溃,客户端表现为 2013(查询过程中断连),与 MySQL 手册 — 2013 / CR_SERVER_LOSTB.3.2.7 MySQL server has gone away 中对「断连、未收齐应答」的描述一致。
  4. 与社区报告一致时可查 proxysql.logCannot find charset/collation [utf8mb3]、断言、signal 6ProxySQL crashed. Restarting!
  5. 官方写明带上该修复的发行版(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 做 utf8mb3utf8mb4 类替换并充分验证。
标签云