问题描述
myloader导入数据时报错
[root@localhost data]# myloader -o -v 3 -P 3306 -u root -p 'Qwer@123' -h 127.0.0.1 -d /dts/data -t 1 --serialized-table-creation --no-data -L /dts/log/table_structure_myloader.log
Segmentation fault
mydumper版本:mydumper-0.12.5-3
影响范围
告警信息
排查步骤
线下测试环境进行复现。
- 安装依赖包
yum install pcre-devel.x86_64 glib2-devel zlib-devel cmake git
yum install -y mysql-devel
yum install -y Percona-Server-devel-57
yum install -y mariadb-devel
yum install -y pcre2-devel # 最新版本20需要
2. 下载mydumper
https://github.com/mydumper/mydumper/tree/v0.12.5-3
# git clone https://github.com/mydumper/mydumper.git #最新版
3. 安装
cd mydumper-0.12.5-3
mkdir build
cd build/
cmake ../
make
make install
4. 配置环境变量
export PATH=$PATH:/usr/local/bin
5. 创建数据库和表
drop database test;
create database test;
use test;
create table t(id int);
insert into t values(1);
create table "t1"(id int);
insert into "t1" values(1);
6. 导出
mydumper -e -v 3 \
-L /dts/log/mydumper.log \
-t 4 \
-h 127.0.0.1 -P 3306 \
-u root -p 'Qwer@123' \
-o /dts/data \
--regex='^(?!mysql.|information_schema.|performance_schema.|sys.)' \
--tz-utc -c -R -E \
--ssl-mode=REQUIRED \
--less-locking --lock-all-tables --no-backup-locks \
--database='test' \
| tee -a /dts/log/mydumper.log
7. gdb 调试
gdb --args myloader -o -v 3 -P 3306 -u root -p 'Qwer@123' -h 127.0.0.1 \
-d /dts/data -t 1 --serialized-table-creation --no-data \
-L /dts/log/table_structure_myloader.log
8. gdb调试结果
[root@localhost data]# gdb --args myloader -o -v 3 -P 3306 -u root -p 'Qwer@123' -h 127.0.0.1 \
> -d /dts/data -t 1 --serialized-table-creation --no-data \
> -L /dts/log/table_structure_myloader.log
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/local/bin/myloader...done.
(gdb) bt
No stack.
(gdb) run
Starting program: /usr/local/bin/myloader -o -v 3 -P 3306 -u root -p Qwer@123 -h 127.0.0.1 -d /dts/data -t 1 --serialized-table-creation --no-data -L /dts/log/table_structure_myloader.log
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7fffef34d700 (LWP 5227)]
[New Thread 0x7fffeeb4c700 (LWP 5228)]
[New Thread 0x7fffee138700 (LWP 5229)]
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff6832b60 in g_str_hash () from /lib64/libglib-2.0.so.0
Missing separate debuginfos, use: debuginfo-install glib2-2.56.1-9.el7_9.x86_64 glibc-2.17-326.el7_9.3.x86_64 libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64 pcre-8.32-17.el7.x86_64 zlib-1.2.7-21.el7_9.x86_64
(gdb) bt
#0 0x00007ffff6832b60 in g_str_hash () from /lib64/libglib-2.0.so.0
#1 0x00007ffff6831b6b in g_hash_table_insert_internal () from /lib64/libglib-2.0.so.0
#2 0x000000000040a8de in load_schema (dbt=dbt@entry=0x68fa50, filename=0x6a0c20 "//dts/data/test.t-schema.sql.gz") at /root/mydumper-0.12.5-3/src/myloader_process.c:168
#3 0x000000000040b0f2 in process_table_filename (filename=0x6375b0 "test.t-schema.sql.gz") at /root/mydumper-0.12.5-3/src/myloader_process.c:376
#4 0x000000000040d468 in load_directory_information (conf=conf@entry=0x7fffffffe310) at /root/mydumper-0.12.5-3/src/myloader_directory.c:212
#5 0x000000000040dd14 in restore_from_directory (conf=0x7fffffffe310) at /root/mydumper-0.12.5-3/src/myloader_directory.c:446
#6 0x0000000000406ac1 in main (argc=<optimized out>, argv=0x7fffffffe488) at /root/mydumper-0.12.5-3/src/myloader.c:395
(gdb) f 2
#2 0x000000000040a8de in load_schema (dbt=dbt@entry=0x68fa50, filename=0x6a0c20 "//dts/data/test.t-schema.sql.gz") at /root/mydumper-0.12.5-3/src/myloader_process.c:168
168 g_hash_table_insert(tbl_hash, dbt->real_table, dbt->real_table);
(gdb) list
163 gchar** create_table= g_strsplit(data->str, "`", 3);
164 dbt->real_table=g_strdup(create_table[1]);
165 if ( g_str_has_prefix(dbt->table,"mydumper_")){
166 g_hash_table_insert(tbl_hash, dbt->table, dbt->real_table);
167 }else{
168 g_hash_table_insert(tbl_hash, dbt->real_table, dbt->real_table);
169 }
170 g_strfreev(create_table);
171 if (append_if_not_exist){
172 if ((g_strstr_len(data->str,13,"CREATE TABLE ")) && !(g_strstr_len(data->str,15,"CREATE TABLE IF"))){
(gdb) info locals
create_table = 0x6a0570
infile = 0x68fd20
is_compressed = 1
eof = 0
data = <optimized out>
create_table_statement = 0x61ee60
line = 7
rj = <optimized out>
(gdb) p dbt->real_table
$1 = 0x0
(gdb) p create_table[0]
$2 = (gchar *) 0x69e0c0 "CREATE TABLE "t" (\n "id" int(11) DEFAULT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=latin1;\n"
(gdb) p create_table[1]
$3 = (gchar *) 0x0
(gdb) p data->str
value has been optimized out
(gdb)
问题原因
- 查看崩溃点
(gdb) run
Starting program: /usr/local/bin/myloader ...
[Thread debugging using libthread_db enabled]
...
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff6832b60 in g_str_hash () from /lib64/libglib-2.0.so.0
...
关键在最后:
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff6832b60 in g_str_hash () from /lib64/libglib-2.0.so.0
SIGSEGV:段错误,典型的“访问了非法地址”(比如 NULL 指针、野指针)。- 当前 CPU 停在函数:
g_str_hash,属于 glib 库。
第一结论:
程序访问了非法内存,崩溃点在 glib 的 g_str_hash() 里。
bt看调用栈
(gdb) bt
#0 0x00007ffff6832b60 in g_str_hash () from /lib64/libglib-2.0.so.0
#1 0x00007ffff6831b6b in g_hash_table_insert_internal () from /lib64/libglib-2.0.so.0
#2 0x000000000040a8de in load_schema (...) at /root/mydumper-0.12.5-3/src/myloader_process.c:168
#3 0x000000000040b0f2 in process_table_filename (...)
#4 0x000000000040d468 in load_directory_information (...)
#5 0x000000000040dd14 in restore_from_directory (...)
#6 0x0000000000406ac1 in main (...)
调用栈是从当前函数往上,一层层谁调用了谁:
#0:当前停在这里 →g_str_hash()(glib 库)#1:谁调用了g_str_hash?→g_hash_table_insert_internal#2:谁调用了g_hash_table_insert_internal?→ 代码里的load_schema- 再往上:
process_table_filename→load_directory_information→restore_from_directory→main
#0往往是库函数/汇编里的 crash 现场;- 第一个属于自己源码的栈帧(这里是
#2 load_schema)就是重点调查对象。
所以现在我们有第二个结论:
- 崩溃是在 glib 的
g_str_hash中, - 但**“问题的入口”是
load_schema()函数第 168 行。**
- 切到问题函数:
frame 2
(gdb) f 2
#2 0x000000000040a8de in load_schema (...) at .../myloader_process.c:168
168 g_hash_table_insert(tbl_hash, dbt->real_table, dbt->real_table);
load_schema() 正在往一个 Hash 表里插入 key、value,
key 和 value 都是 dbt->real_table,
然后没跑完就崩了。
list看源码上下文
(gdb) list
163 gchar** create_table= g_strsplit(data->str, "`", 3);
164 dbt->real_table=g_strdup(create_table[1]);
165 if ( g_str_has_prefix(dbt->table,"mydumper_")){
166 g_hash_table_insert(tbl_hash, dbt->table, dbt->real_table);
167 }else{
168 g_hash_table_insert(tbl_hash, dbt->real_table, dbt->real_table);
169 }
170 g_strfreev(create_table);
171 if (append_if_not_exist){
172 if ((g_strstr_len(data->str,13,"CREATE TABLE ")) && !(g_strstr_len(data->str,15,"CREATE TABLE IF"))){
gchar** create_table = g_strsplit(data->str, "", 3);`
-
data->str:一整行 SQL 文本,比如CREATE TABLE ...g_strsplit:按某个分隔符把字符串拆成若干段,返回字符串数组(gchar**类似char**)。- 这里是:按 反引号 ` 拆,最多拆 3 段。
dbt->real_table = g_strdup(create_table[1]);
-
g_strdup:相当于strdup,把字符串复制一份。create_table[1]理论上应该是表名(比如从CREATE TABLEt``中取出t)。
if (g_str_has_prefix(dbt->table, "mydumper_")) {
-
- 判断内部控制表(mydumper 自己的)。
g_hash_table_insert(tbl_hash, ..., dbt->real_table);
-
- 往 glib 的 hash 表里插入键值对。
tbl_hash是一个GHashTable*。 - key 要求是一个有效的字符串指针,因为后面会用
g_str_hash()计算 hash 值。
- 往 glib 的 hash 表里插入键值对。
所以这段逻辑的意图是:
从 data->str 这行 SQL 中解析出表名,存到 dbt->real_table,
然后把它作为 key/value 插入到一个哈希表里。
info locals看局部变量:
(gdb) info locals
create_table = 0x6a0570
infile = 0x68fd20
is_compressed = 1
eof = 0
data = <optimized out>
create_table_statement = 0x61ee60
line = 7
rj = <optimized out>
create_table是一个指针,指向“字符串指针数组”的起始位置,也就是gchar**。data被优化掉了(编译器优化),gdb 看不到它具体内容,这在 Release 编译很常见。- 对我们当前问题来说,重点是
create_table里面装了什么。
- 用
p打印指针指向的内容
(gdb) p dbt->real_table
$1 = 0x0
(gdb) p create_table[0]
$2 = (gchar *) 0x69e0c0 "CREATE TABLE "t" (\n "id" int(11) DEFAULT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=latin1;\n"
(gdb) p create_table[1]
$3 = (gchar *) 0x0
p dbt->real_table
-
- 输出是
0x0,也就是 NULL 指针 → 表示它现在是空的,没有指向有效内存。
- 输出是
p create_table[0]
-
- 是指针,指向一串字符串:
"CREATE TABLE "t" (
"id" int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;\n"
-
- 说明:
g_strsplit()把整行 SQL 放在了create_table[0]。
- 说明:
p create_table[1]
-
- 是
0x0,NULL。
- 是
回忆一下 g_strsplit 的调用:
gchar** create_table = g_strsplit(data->str, "`", 3);
它按**反引号 `来拆分。
**但是我们的这行 SQL 用的是双引号 " :
CREATE TABLE "t" ( ... )
所以:
- 这整行里 根本没有反引号 ` ;
- 对
g_strsplit(字符串, "", 3)` 来说:
拆不出什么东西 → 整行只会作为第一个元素:
create_table[0] = 原始字符串
create_table[1] = NULL
create_table[2] = NULL
然后下一行代码是:
dbt->real_table = g_strdup(create_table[1]);
- 这里等价于:
g_strdup(NULL) - 通常实现会直接返回 NULL → 所以
dbt->real_table也是 NULL。
再往下:
g_hash_table_insert(tbl_hash, dbt->real_table, dbt->real_table);
此时传的其实是:
g_hash_table_insert(tbl_hash, NULL, NULL);
glib 在内部要拿 key 去算 hash:
g_str_hash(key); // key = NULL
对 NULL 指针做字符串操作 → 直接段错误(SIGSEGV)。
这就是最开始看到的:
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff6832b60 in g_str_hash ()
整个因果链条就串起来了:
解析表名的逻辑假设了 SQL 里一定有反引号 table,
但实际 SQL 用的是 "table"(可能受 ANSI_QUOTES 等影响),
导致解析失败 → 表名变成 NULL → 往 hash 里塞 NULL key → glib hash 时报错崩溃。
最后,聊聊p data->str 为啥说 “optimized out”?
(gdb) p data->str
value has been optimized out
- 编译时使用了优化(例如
-O2之类),编译器把某些局部变量做了优化处理(寄存器分配、消除等)。 - gdb 找不到这个变量的“原始形式”了,就会告诉你“被优化掉了”。
如何改善?
- 用 Debug 模式重新编译程序(禁用或降低优化,保留更多符号信息):
cd /root/mydumper
rm -rf build
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j$(nproc)
- 下次 gdb 调试时,
data->str之类的变量就更容易看到真实值。
myloader crash 原因:
load_schema() 里用 g_strsplit(…, "")解析CREATE TABLE "t" 这一行, 因为没有反引号,表名解析失败,dbt->real_table变成 NULL, 然后被当作 key 塞进g_hash_table_insert,glib 里对 NULL 做 g_str_hash`,直接 SIGSEGV。
解决方案
临时解决方案
表名被双引号括起来的原因是SQL_MODE中的 ANSI_QUOTES 引起的:dev.mysql.com/doc/refman/…
所以需要修改sql_mode
SET global sql_mode = sys.list_drop(@@sql_mode, 'ANSI_QUOTES');
彻底解决方案
- 升级mydumper到最新版本:mydumper在0.20.1-1支持了myloader指定 identifier_quote_character_str
[root@localhost data]# cat metadata
# Started dump at: 2025-11-21 04:37:47
[config]
quote-character = DOUBLE_QUOTE
[myloader_session_variables]
SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ANSI_QUOTES,ONLY_FULL_GROUP_BY,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' /*!40101
["test"."t"]
real_table_name=t
rows = 1
["test"."t1"]
real_table_name=t1
rows = 1
[config]
max-statement-size = 310
num-sequences = 0
# Finished dump at: 2025-11-21 04:37:47
myloader_session_variables默认导出了SQL_MODE。
- 修改当前版本的源代码
- 修改文件
vim /root/mydumper-0.12.5-3/src/myloader_process.c
-
- 找到这一段
gchar** create_table= g_strsplit(data->str, "`", 3);
dbt->real_table=g_strdup(create_table[1]);
if ( g_str_has_prefix(dbt->table,"mydumper_")){
g_hash_table_insert(tbl_hash, dbt->table, dbt->real_table);
}else{
g_hash_table_insert(tbl_hash, dbt->real_table, dbt->real_table);
}
g_strfreev(create_table);
-
- 把它整段替换成下面这段(包含单引号 + 双引号兼容 + NULL 防护)
/* 先尝试用反引号 ` 拆分:CREATE TABLE `t` (...) */
gchar **create_table = g_strsplit(data->str, "`", 3);
/* 如果反引号没拆出表名,再尝试用双引号 "(兼容 ANSI_QUOTES 场景:CREATE TABLE "t" (...)) */
if (create_table[1] == NULL) {
g_strfreev(create_table);
create_table = g_strsplit(data->str, """, 3);
}
/* 只有在确实拆出了表名时,才设置 real_table 并插入 hash 表,避免传 NULL 给 g_str_hash */
if (create_table[1] != NULL) {
dbt->real_table = g_strdup(create_table[1]);
if (g_str_has_prefix(dbt->table, "mydumper_")) {
/* mydumper 内部表,用 dbt->table 作为 key */
g_hash_table_insert(tbl_hash, dbt->table, dbt->real_table);
} else {
/* 普通业务表,用真实表名作为 key */
g_hash_table_insert(tbl_hash, dbt->real_table, dbt->real_table);
}
} else {
/* 这行 schema 既没有 `table` 也没有 "table",给个 warning,跳过这条 */
g_warning("Could not parse table name from schema line: %s", data->str);
}
/* 释放 split 出来的字符串数组 */
g_strfreev(create_table);
- 重新编译
cd /root/mydumper-0.12.5-3
rm -rf build
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j$(nproc)
make install
- 重新执行
[root@localhost build]# mysqlshow
+--------------------+
| Databases |
+--------------------+
| information_schema |
| data_recovered |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
[root@localhost build]# mysqladmin drop test
Dropping the database is potentially a very bad thing to do.
Any data stored in the database will be destroyed.
Do you really want to drop the 'test' database [y/N] y
Database "test" dropped
[root@localhost build]# mysqlshow
+--------------------+
| Databases |
+--------------------+
| information_schema |
| data_recovered |
| mysql |
| performance_schema |
| sys |
+--------------------+
[root@localhost build]#
[root@localhost build]# myloader -o -v 3 -P 3306 -u root -p 'Qwer@123' -h 127.0.0.1 \
> -d /dts/data -t 1 --serialized-table-creation --no-data \
> -L /dts/log/table_structure_myloader.log
这里会发现myloader虽然不报错了,但是导入会报错。报错如下
原因是myloader导入的时候会设置sql_mode
需要继续修改这一段代码,放弃了。
正常应该在mydumper阶段设置sql_mode,在进行导出,myloader就无需修改代码了。