当前位置: 首页 > news >正文

探讨前端与后端的安全策略:保护用户数据的关键措施

CTE 查询数据量过大导致 MySQL 8.0 发生 CORE 问题解析

一、问题发现

在客户现场的一次问题报告中发现某个带有 CTE 语句进行查询的时候,数据量少的时候没问题,但是数据量大的时候会导致 core。注意:这个问题只在 MySQL 8.0.32 版本才会复现,最新的 8.4.4 版本没有问题。

看下面例子:

1、准备表

CREATE TABLE t1 (

ORG_ID decimal(12,0) NOT NULL,

ORG_NAME varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,

ORG_CODE varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,

ORG_TYPE varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT (_utf8mb4'01'),

ORG_LEVEL varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

PORG_ID decimal(12,0) DEFAULT NULL,

ORG_STATE varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,

COMMENTS varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

MANAGER_FLAG varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

SOET_WEEKLY varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

CONTACT_NAME varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

CONTACT_TELEPHONE decimal(32,0) DEFAULT NULL,

OTHER_TELEPHONE decimal(32,0) DEFAULT NULL,

OFFICE_ADDR varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

PROVINCE varchar(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

CITY varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

COUNTRY varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

ORG_CLASS varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

UPDATE_TIME datetime DEFAULT NULL,

OPERATE_TYPE varchar(3) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

CREATE_TIME datetime DEFAULT NULL,

KEY IDX_t1 (ORG_ID,PORG_ID),

KEY INX_ESOP_ORG_OID (ORG_ID),

KEY IDX_PROVINCE_CITY (PROVINCE,CITY)

);

INSERT INTO t1 VALUES(29100709,'数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识',29100709,01,5,291007,1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,290,9150,915009,02,'2018-02-09 04:19:00',1,'2017-11-15 00:00:00');

INSERT INTO t1 VALUES(29100708,'数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识',29100709,01,5,291007,1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,290,9150,915009,02,'2018-02-09 04:19:00',1,'2017-11-15 00:00:00');

INSERT INTO t1 VALUES(29100707,'数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识数据库数据知识',29100709,01,5,291007,1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,290,9150,915009,02,'2018-02-09 04:19:00',1,'2017-11-15 00:00:00');

INSERT INTO t1 SELECT * FROM t1;

INSERT INTO t1 SELECT * FROM t1;

CREATE TABLE t2 (

USER_ID decimal(14,0) NOT NULL,

USER_NAME varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,

NICK_NAME varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

USER_CODE varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NOT NULL,

PASS_WORD varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

ORG_ID decimal(12,0) DEFAULT NULL,

POSITION varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

FUNCTIONS varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

MOBILE_NO varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

EXTENSION_NO varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

EMAIL varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

STATE varchar(4) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

COMMENTS varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

SCORE decimal(10,0) DEFAULT NULL,

IDCARD varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

AUDIT_STATE decimal(1,0) DEFAULT NULL,

UPDATE_TIME datetime DEFAULT NULL,

OPERATE_TYPE varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

MAPPINGCODE varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin DEFAULT NULL,

CREATE_TIME datetime DEFAULT NULL,

KEY IDX_t2_ID (USER_ID,USER_CODE),

KEY INX_EUS_MCOD (MAPPINGCODE),

KEY INX_EUERS_UNM (USER_NAME),

KEY IDX_t2_MOBILE_NO (MOBILE_NO),

KEY INX_ESOP_USR_ID (USER_ID),

KEY IDX_t2_USER_CODE (USER_CODE),

KEY IDX_ORG_ID (ORG_ID)

);

INSERT INTO t2(USER_ID, user_name, user_code, org_id) VALUES(1001, 'daizhong', 'daizhong', 1);

INSERT INTO t2(USER_ID, user_name, user_code, org_id) VALUES(1002, 'daizhong1', 'daizhong1', 29100709);

INSERT INTO t2(USER_ID, user_name, user_code, org_id) VALUES(1003, 'daizhong1', 'daizhong1', 29100708);

INSERT INTO t2(USER_ID, user_name, user_code, org_id) VALUES(1003, 'daizhong1', 'daizhong1', 29100707);

INSERT INTO t2 SELECT * FROM t2;

INSERT INTO t2 SELECT * FROM t2;

2、tmp_table_size 为默认值场合

如下所示用默认 tmp_table_size 并且进行 CTE 派生表查询,可以发现结果正常显示一条数据,符合预期。

-- tmp_table_size参数为默认值的情况

greatsql> SHOW variables LIKE '%tmp_table_size%';

+----------------+----------+

| Variable_name | Value |

+----------------+----------+

| tmp_table_size | 16777216 |

+----------------+----------+

greatsql> WITH RECURSIVE cte_tree AS ( SELECT 1 AS LEVEL, t.org_id,t.org_id as porg_id,user_name, nick_name, user_code FROM t2 t UNION ALL SELECT LEVEL + 1 AS LEVEL, pt.org_id,pt.porg_id,pt.ORG_NAME,pt.CONTACT_NAME,pt.ORG_CODE FROM t1 pt JOIN cte_tree ct ON pt.org_id = ct.porg_id ) SELECT a.* FROM cte_tree a limit 1;

+-------+--------+---------+-----------+-----------+-----------+

| LEVEL | org_id | porg_id | user_name | nick_name | user_code |

+-------+--------+---------+-----------+-----------+-----------+

| 1 | 1 | 1 | daizhong | NULL | daizhong |

+-------+--------+---------+-----------+-----------+-----------+

3、tmp_table_size 修改小的场合

为了不让之前的临时表影响 RAM 内存的阈值,重新启动数据库。然后执行以下命令。

-- 设置tmp_table_size参数值比临时表数据大的情况

greatsql> SET tmp_table_size=1342;

-- 以下同样的命令导致core

greatsql> WITH RECURSIVE cte_tree AS ( SELECT 1 AS LEVEL, t.org_id,t.org_id as porg_id,user_name, nick_name, user_code FROM t2 t UNION ALL SELECT LEVEL + 1 AS LEVEL, pt.org_id,pt.porg_id,pt.ORG_NAME,pt.CONTACT_NAME,pt.ORG_CODE FROM t1 pt JOIN cte_tree ct ON pt.org_id = ct.porg_id ) SELECT a.* FROM cte_tree a limit 1;

core堆栈如下:

Thread 56 "mysqld" received signal SIGABRT, Aborted.

__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50

50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.

(gdb) bt

#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50

#1 0x00007ffff7508859 in __GI_abort () at abort.c:79

#2 0x00007ffff7508729 in __assert_fail_base (

fmt=0x7ffff769e588 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n",

assertion=0x55555dd91569 "m_opened_table != nullptr",

file=0x55555dd91510 "storage/temptable/include/temptable/handler.h", line=563, function=) at assert.c:94

#3 0x00007ffff7519fd6 in __GI___assert_fail (

assertion=0x55555dd91569 "m_opened_table != nullptr",

file=0x55555dd91510 "storage/temptable/include/temptable/handler.h", line=563,

function=0x55555dd914d8 "void temptable::Handler::opened_table_validate()")

at assert.c:103

#4 0x000055555bb98e96 in temptable::Handler::opened_table_validate (this=0x7fff2020f7e8)

at storage/temptable/include/temptable/handler.h:563

#5 0x000055555bb95354 in temptable::Handler::info (this=0x7fff2020f7e8)

at storage/temptable/src/handler.cc:751

#6 0x000055555bb93482 in temptable::Handler::open (this=0x7fff2020f7e8,

table_name=0x7fff2020b170 "greatdb/confper/tmp/#sqlf1fea_8_2")

at storage/temptable/src/handler.cc:218

#7 0x0000555558fd1804 in handler::ha_open (this=0x7fff2020f7e8, table_arg=0x7fff2020edf0,

name=0x7fff2020b170 "greatdb/confper/tmp/#sqlf1fea_8_2", mode=2,

test_if_locked=516, table_def=0x0)

at sql/handler.cc:2927

#8 0x00005555597c223e in open_tmp_table (table=0x7fff2020edf0)

at sql/sql_tmp_table.cc:2422

#9 0x000055555923a798 in FollowTailIterator::Init (this=0x7fff2021e9c0)

at sql/iterators/basic_row_iterators.cc:345

#10 0x0000555559255c13 in FilterIterator::Init (this=0x7fff2021ea20)

at sql/iterators/composite_iterators.h:85

#11 0x0000555559b8fb69 in NestedLoopIterator::Init (this=0x7fff2021ea90)

at sql/iterators/composite_iterators.cc:523

#12 0x0000555559b9805b in MaterializeIterator::MaterializeQueryBlock (

this=0x7fff2021ec08, query_block=..., stored_rows=0x7fffc83eedd8)

at sql/iterators/composite_iterators.cc:1221

#13 0x0000555559b979f1 in MaterializeIterator::MaterializeRecursive (

this=0x7fff2021ec08)

at sql/iterators/composite_iterators.cc:1129

#14 0x0000555559b960b9 in MaterializeIterator::Init (

this=0x7fff2021ec08)

at sql/iterators/composite_iterators.cc:971

#15 0x0000555559b8e859 in LimitOffsetIterator::Init (this=0x7fff2021eca8)

at sql/iterators/composite_iterators.cc:100

#16 0x00005555597dacd5 in Query_expression::ExecuteIteratorQuery (this=0x7fff201c20d0,

thd=0x7fff20001050) at sql/sql_union.cc:1814

#17 0x00005555597db113 in Query_expression::execute (this=0x7fff201c20d0, thd=0x7fff20001050)

at sql/sql_union.cc:1877

#18 0x00005555597001c0 in Sql_cmd_dml::execute_inner (this=0x7fff20200a80,

thd=0x7fff20001050) at sql/sql_select.cc:872

#19 0x00005555596ff38e in Sql_cmd_dml::execute (this=0x7fff20200a80, thd=0x7fff20001050)

at sql/sql_select.cc:612

#20 0x000055555966207c in mysql_execute_command (thd=0x7fff20001050, first_level=true)

at sql/sql_parse.cc:5227

打印m_opened_table变量,发现结果为空,所以导致core

#4 0x000055555bb98e96 in temptable::Handler::opened_table_validate (this=0x7fff2c20ed88)

at storage/temptable/include/temptable/handler.h:563

563 assert(m_opened_table != nullptr);

(gdb) p m_opened_table

$21 = (temptable::Table *) 0x0

二、问题调查过程

查询带有 CTE 派生表的时候内部会创建临时表用于保存临时数据,因此先看一下上面 2 个场合的临时表情况:

| tmp_table_size 值 | 查询开始时候临时表情况 | 查询过程中临时表情况 | 说明 | | ---------------- | ---------------------- | -------------------- | ------ | | 16777216 | 内存表 | 内存表 | 没问题 | | 1342 | 内存表 | 转为落盘表 | CORE |

1、正常执行的代码流程调查

先看一下 tmp_table_size=16777216 时候没问题的代码流程,以下代码通过 Handler::create() 函数向 kv_store 这个 map 插入了一条新的 table 信息。

首先创建内部物化表,代码调用流程如下:

Table_ref::create_materialized_table -> instantiate_tmp_table -> create_tmp_table_with_fallback

-> temptable::Handler::create -> temptable::Allocator

-> temptable::Prefer_RAM_over_MMAP_policy_obeying_per_table_limit::block_source

bool instantiate_tmp_table(THD *thd, TABLE *table) {

// 这里创建临时表,包括引擎,调用Handler::create,注意这里handler=temptable::Handler

if (create_tmp_table_with_fallback(thd, table)) return true;

// 这里打开表,调用Handler::open打开临时表引擎

open_tmp_table(table);

}

int Handler::create(const char *table_name, TABLE *mysql_table,

HA_CREATE_INFO *, dd::Table *) {

try {

size_t per_table_limit = thd_get_tmp_table_size(ha_thd());

auto &kv_store = kv_store_shard[thd_thread_id(ha_thd())];

// 这里创建表引擎的时候申请新的内存用于保存table信息,kv_store.emplace调用下面的block_source()函数申请新的内存。

const auto insert_result = kv_store.emplace(

std::piecewise_construct, std::forward_as_tuple(table_name),

std::forward_as_tuple(mysql_table, m_shared_block,

all_columns_are_fixed_size, per_table_limit));

ret = insert_result.second ? Result::OK : Result::TABLE_EXIST;

} catch (Result ex) {

ret = ex;

} catch (...) {

ret = Result::OUT_OF_MEM;

}

}

// 申请新的内存

struct Prefer_RAM_over_MMAP_policy_obeying_per_table_limit {

static Source block_source(uint32_t block_size,

TableResourceMonitor *table_resource_monitor) {

assert(table_resource_monitor);

assert(table_resource_monitor->consumption() <=

table_resource_monitor->threshold());

/* 以下各参数值:

table_resource_monitor->consumption() = 0 这个值为已经申请到的内存值

block_size = 1048576 这个值为当前新申请的内存值为1MB内存,为新申请的块大小

table_resource_monitor->threshold() = 16777216 这个值为当前tmp_table_size值

这个比较结果可以看出没有超出tmp_table_size值,因此正常新增1048576内存。

*/

if (table_resource_monitor->consumption() + block_size >

table_resource_monitor->threshold())

throw Result::RECORD_FILE_FULL;

return Prefer_RAM_over_MMAP_policy::block_source(block_size);

}

};

接着 FollowTailIterator 迭代器通过 open_tmp_table 函数用 temptable::Handler::open 函数在 kv_store 这个 map 寻找刚才插入的那条记录成功。

int Handler::open(const char *table_name, int, uint, const dd::Table *) {

try {

auto &kv_store = kv_store_shard[thd_thread_id(ha_thd())];

m_opened_table = kv_store.find(table_name);

if (m_opened_table) {

ret = Result::OK;

opened_table_validate();

} else {

ret = Result::NO_SUCH_TABLE;

}

} catch (std::bad_alloc &) {

ret = Result::OUT_OF_MEM;

}

info(HA_STATUS_VARIABLE);

DBUG_RET(ret);

}

2、导致 core 的代码流程调查

设置完 tmp_table_size=1342,这里代码流程跟上面是一样的,但是有一些参数值有变化。

bool instantiate_tmp_table(THD *thd, TABLE *table) {

// 这里创建临时表,包括引擎,调用Handler::create,因为创建临时表内存超过阈值,因此这里引擎改为落盘表,hangler=ha_innobase

if (create_tmp_table_with_fallback(thd, table)) return true;

// 这里打开落盘表,调用Handler::open

open_tmp_table(table);

}

static bool create_tmp_table_with_fallback(THD *thd, TABLE *table) {

// 一开始临时表handler=temptable::Handler,但是申请内存超过阈值

int error =

table->file->create(share->table_name.str, table, &create_info, nullptr);

if (error == HA_ERR_RECORD_FILE_FULL &&

table->s->db_type() == temptable_hton) {

// 这里修改临时表handler=ha_innobase

table->file = get_new_handler(

table->s, false, share->alloc_for_tmp_file_handler, innodb_hton);

error = table->file->create(share->table_name.str, table, &create_info,

nullptr);

}

}

int Handler::create(const char *table_name, TABLE *mysql_table,

HA_CREATE_INFO *, dd::Table *) {

try {

size_t per_table_limit = thd_get_tmp_table_size(ha_thd());

auto &kv_store = kv_store_shard[thd_thread_id(ha_thd())];

// 这里创建表引擎的时候申请新的内存用于保存table信息,kv_store.emplace调用下面的block_source()函数申请新的内存。

// 这里因为新申请的内存大于tmp_table_size=1342这个阈值,因此报错RECORD_FILE_FULL,这回kv_store这个map因为报错因此没有插入table信息。

const auto insert_result = kv_store.emplace(

std::piecewise_construct, std::forward_as_tuple(table_name),

std::forward_as_tuple(mysql_table, m_shared_block,

all_columns_are_fixed_size, per_table_limit));

ret = insert_result.second ? Result::OK : Result::TABLE_EXIST;

} catch (Result ex) {

ret = ex;

} catch (...) {

ret = Result::OUT_OF_MEM;

}

}

// 申请新的内存

struct Prefer_RAM_over_MMAP_policy_obeying_per_table_limit {

static Source block_source(uint32_t block_size,

TableResourceMonitor *table_resource_monitor) {

assert(table_resource_monitor);

assert(table_resource_monitor->consumption() <=

table_resource_monitor->threshold());

/* 以下各参数值:

table_resource_monitor->consumption() = 0 这个值为已经申请到的内存值

block_size = 1048576 这个值为当前新申请的内存值

table_resource_monitor->threshold() = 1342 这个值为当前tmp_table_size值

这个比较结果可以看出这次新申请的内存超出tmp_table_size值,因此这次返回Result::RECORD_FILE_FULL的错误。

*/

if (table_resource_monitor->consumption() + block_size >

table_resource_monitor->threshold())

throw Result::RECORD_FILE_FULL;

return Prefer_RAM_over_MMAP_policy::block_source(block_size);

}

};

接着继续执行 sql,这次在 FollowTailIterator 迭代器打开临时表,注意,这里临时表引擎还是 temptable::Handler 而不是 ha_innobase,那是因为 FollowTailIterator 迭代器用到的临时表跟上面 MaterializeIterator 迭代器用到的临时表是在 prepare 阶段,通过 Common_table_expr::clone_tmp_table 函数拷贝出来的,所以他们的 handler 虽然类型相同但是地址不同,只有 table->share 相同。

打开这张临时表,在 kv_store 寻找临时表信息的时候找不到对应临时表信息,导致 m_opened_table 为空,接着调用 info () 函数的时候 core 了。

实际上这时候 FollowTailIterator 迭代器用到的临时表的引擎应该跟上面的临时表一样改为 innodb 引擎,因为这里引擎没有跟着改动,加上 kv_store 没有存放相关临时表信息,因此导致这里 core 了。

zq.zhaopin.com/moment/25673659
zq.zhaopin.com/moment/25673544
zq.zhaopin.com/moment/25673539
zq.zhaopin.com/moment/25674955
zq.zhaopin.com/moment/25674927
zq.zhaopin.com/moment/25674895
zq.zhaopin.com/moment/25674871
zq.zhaopin.com/moment/25674826
zq.zhaopin.com/moment/25674794
zq.zhaopin.com/moment/25674765
zq.zhaopin.com/moment/25674754
zq.zhaopin.com/moment/25674698
zq.zhaopin.com/moment/25674688
zq.zhaopin.com/moment/25674681
zq.zhaopin.com/moment/25674669
zq.zhaopin.com/moment/25674660
zq.zhaopin.com/moment/25674653
zq.zhaopin.com/moment/25674631
zq.zhaopin.com/moment/25674625
zq.zhaopin.com/moment/25674620
zq.zhaopin.com/moment/25674592
zq.zhaopin.com/moment/25674421
zq.zhaopin.com/moment/25674255
zq.zhaopin.com/moment/25673902
zq.zhaopin.com/moment/25673795
zq.zhaopin.com/moment/25673741
zq.zhaopin.com/moment/25673705
zq.zhaopin.com/moment/25673660
zq.zhaopin.com/moment/25679456
zq.zhaopin.com/moment/25679227
zq.zhaopin.com/moment/25679084
zq.zhaopin.com/moment/25678594
zq.zhaopin.com/moment/25678241
zq.zhaopin.com/moment/25678110
zq.zhaopin.com/moment/25678089
zq.zhaopin.com/moment/25678040
zq.zhaopin.com/moment/25678008
zq.zhaopin.com/moment/25677884
zq.zhaopin.com/moment/25677855
zq.zhaopin.com/moment/25677832
zq.zhaopin.com/moment/25677777
zq.zhaopin.com/moment/25677740
zq.zhaopin.com/moment/25677636
zq.zhaopin.com/moment/25677614
zq.zhaopin.com/moment/25677535
zq.zhaopin.com/moment/25677495
zq.zhaopin.com/moment/25677432
zq.zhaopin.com/moment/25677349
zq.zhaopin.com/moment/25677262
zq.zhaopin.com/moment/25677210
zq.zhaopin.com/moment/25677173
zq.zhaopin.com/moment/25679237
zq.zhaopin.com/moment/25679209
zq.zhaopin.com/moment/25679161
zq.zhaopin.com/moment/25679059
zq.zhaopin.com/moment/25679029
zq.zhaopin.com/moment/25678967
zq.zhaopin.com/moment/25678896
zq.zhaopin.com/moment/25678637
zq.zhaopin.com/moment/25678222
zq.zhaopin.com/moment/25678106
zq.zhaopin.com/moment/25678079
zq.zhaopin.com/moment/25678050
zq.zhaopin.com/moment/25677971
zq.zhaopin.com/moment/25677806
zq.zhaopin.com/moment/25677781
zq.zhaopin.com/moment/25677741
zq.zhaopin.com/moment/25677696
zq.zhaopin.com/moment/25677512
zq.zhaopin.com/moment/25677175
zq.zhaopin.com/moment/25679214
zq.zhaopin.com/moment/25679176
zq.zhaopin.com/moment/25678994
zq.zhaopin.com/moment/25678742
zq.zhaopin.com/moment/25678642
zq.zhaopin.com/moment/25678504
zq.zhaopin.com/moment/25678393
zq.zhaopin.com/moment/25678039
zq.zhaopin.com/moment/25677956
zq.zhaopin.com/moment/25677893
zq.zhaopin.com/moment/25677838
zq.zhaopin.com/moment/25677798
zq.zhaopin.com/moment/25677766
zq.zhaopin.com/moment/25677694
zq.zhaopin.com/moment/25677668
zq.zhaopin.com/moment/25677537
zq.zhaopin.com/moment/25677468
zq.zhaopin.com/moment/25677426
zq.zhaopin.com/moment/25677395
zq.zhaopin.com/moment/25677359
zq.zhaopin.com/moment/25677310
zq.zhaopin.com/moment/25677222
zq.zhaopin.com/moment/25677181
zq.zhaopin.com/moment/25679635
zq.zhaopin.com/moment/25679298
zq.zhaopin.com/moment/25679236
zq.zhaopin.com/moment/25679189
zq.zhaopin.com/moment/25679132
zq.zhaopin.com/moment/25679043
zq.zhaopin.com/moment/25679017
zq.zhaopin.com/moment/25678868
zq.zhaopin.com/moment/25678432
zq.zhaopin.com/moment/25678121
zq.zhaopin.com/moment/25678056
zq.zhaopin.com/moment/25678019
zq.zhaopin.com/moment/25677997
zq.zhaopin.com/moment/25677754
zq.zhaopin.com/moment/25677720
zq.zhaopin.com/moment/25677606
zq.zhaopin.com/moment/25677497
zq.zhaopin.com/moment/25677460
zq.zhaopin.com/moment/25677437
zq.zhaopin.com/moment/25677417
zq.zhaopin.com/moment/25677328
zq.zhaopin.com/moment/25677291
zq.zhaopin.com/moment/25677227
zq.zhaopin.com/moment/25665803
zq.zhaopin.com/moment/25665757
zq.zhaopin.com/moment/25665567
zq.zhaopin.com/moment/25665540
zq.zhaopin.com/moment/25665514
zq.zhaopin.com/moment/25665491
zq.zhaopin.com/moment/25665469
zq.zhaopin.com/moment/25665426
zq.zhaopin.com/moment/25665399
zq.zhaopin.com/moment/25665362
zq.zhaopin.com/moment/25665337
zq.zhaopin.com/moment/25665309
zq.zhaopin.com/moment/25665281
zq.zhaopin.com/moment/25665247
zq.zhaopin.com/moment/25665217
zq.zhaopin.com/moment/25665198
zq.zhaopin.com/moment/25665176
zq.zhaopin.com/moment/25665127
zq.zhaopin.com/moment/25665090
zq.zhaopin.com/moment/25665041
zq.zhaopin.com/moment/25665008
zq.zhaopin.com/moment/25664984
zq.zhaopin.com/moment/25664944
zq.zhaopin.com/moment/25664916

打开临时表代码流程:

MaterializeIterator::Init -> aterializeIterator::MaterializeRecursive

-> MaterializeIterator::MaterializeQueryBlock

-> NestedLoopIterator::Init

-> FilterIterator::Init

-> FollowTailIterator::Init

-> open_tmp_table

-> handler::ha_open -> temptable::Handler::open

int Handler::open(const char *table_name, int, uint, const dd::Table *) {

try {

auto &kv_store = kv_store_shard[thd_thread_id(ha_thd())];

// 因为上面Handler::create创建的时候,kv_store没有插入临时表信息,因此这里找不到这张临时表的信息,导致这里m_opened_table为空。

m_opened_table = kv_store.find(table_name);

if (m_opened_table) {

ret = Result::OK;

opened_table_validate();

} else {

ret = Result::NO_SUCH_TABLE;

}

} catch (std::bad_alloc &) {

ret = Result::OUT_OF_MEM;

}

info(HA_STATUS_VARIABLE);

DBUG_RET(ret);

}

3、总结问题

根据上面分析可以知道,第二次修改 tmp_table_size 值为较小数值之后 core,原因在于申请内存的时候 tmp_table_size 值超过阈值,因此在 kv_store 插入表信息失败,后面 open 临时表的时候,通过 kv_store 寻找这张表信息没有找到,因此最后 m_opened_table 为空指针。

从上面判断阈值公式其实可以看出这里判断方法有问题,因为实际申请的是 96 大小,但是这里却用块大小 1MB 来进行阈值比较。

if (table_resource_monitor->consumption() + block_size >

table_resource_monitor->threshold())

throw Result::RECORD_FILE_FULL;

三、问题解决

结合上面分析,我们发现问题原因在于内存申请额时候用的判断阈值的公式有问题,导致提前报错使 kv_store 没有插入相关临时表信息,同时 CTE 的其中一个相关临时表引擎改变了,但是其它层的临时表引擎没改,这两个原因导致最后的 core。

我们用较新的 8.4.4 版本代码尝试运行该 sql 发现没有问题,因此可见最新代码已经修复该 bug。查看代码,可以发现有一个如下修复 patch:

Change-Id: I90d7374971bdfc1fc178801d0c793382c6ab8a49

Bug #33814188 - Assertion consumption > threshold

MySQL 8.4.4 版本代码作如下修复,就可以解决这个问题了。

修复storage/temptable/include/temptable/allocator.h文件的如下代码:

删除Prefer_RAM_over_MMAP_policy_obeying_per_table_limit::block_source函数,

申请内存的时候判断阈值从判断新增块大小改为判断实际新增内存大小,

这样第一次申请内存的时候就不会超过tmp_table_size,

在temptable::Handler::create可以正常向kv_store插入临时表信息。

同时在allocate()函数申请完内存之后增加如下阈值判断的操作。这样第二次申请内存的时候就会正常报错。

template

inline T *Allocator::allocate(size_t n_elements) {

// 增加申请内存之后执行判断阈值操作的代码。这里从原来的block_size改为n_bytes_requested来判断,

// 也就是从原来判断新增块大小改为判断实际新增内存大小,这样用实际新增大小来判断阈值。

if (m_table_resource_monitor.consumption() + n_bytes_requested(※注意这个改动) >

m_table_resource_monitor.threshold()) {

throw Result::RECORD_FILE_FULL;

}

}

修改之后的代码调用流程如下:

1、一开始创建正常handler=temptable::Handler临时表,并且向kv_store插入表信息成功。

2、通过handler::ha_write_row在向临时表写数据的时候,因为新申请的内存64kBytes超过了阈值

因此修改handler=ha_innobase,接着通过create_ondisk_from_heap函数创建落盘表。

3、create_ondisk_from_heap函数创建落盘表的时候会把之前CTE拷贝出来的相关表的引擎都一起改为

innodb表,因此接着FollowTailIterator迭代器的表执行open_tmp_table的时候打开的就跟

MaterializeIterator迭代器一样都是innodb表了,就不会有查找临时表handler的kv_store为空的情况了。

接着执行上面的查询,发现可以查出结果了。

greatsql> SET tmp_table_size=1342;

Query OK, 0 rows affected (0.00 sec)

greatsql> WITH RECURSIVE cte_tree AS ( SELECT 1 AS LEVEL, t.org_id,t.org_id as porg_id,user_name, nick_name, user_code FROM t2 t UNION ALL SELECT LEVEL + 1 AS LEVEL, pt.org_id,pt.porg_id,pt.ORG_NAME,pt.CONTACT_NAME,pt.ORG_CODE FROM t1 pt JOIN cte_tree ct ON pt.org_id = ct.porg_id ) SELECT a.* FROM cte_tree a limit 1;

+-------+--------+---------+-----------+-----------+-----------+

| LEVEL | org_id | porg_id | user_name | nick_name | user_code |

+-------+--------+---------+-----------+-----------+-----------+

| 1 | 1 | 1 | daizhong | NULL | daizhong |

+-------+--------+---------+-----------+-----------+-----------+

1 row in set (1 min 1.63 sec)

四、问题总结

通过以上分析我们可以发现,带有 CTE 派生表的查询会在内部创建临时表用于储存中间数据,根据 tmp_table_size 值设置会影响临时表存放地方,如果 tmp_table_size 设置小了那么一开始就会从内存表改为创建落盘表,但是 CTE 内部涉及好几层迭代器,这时候每一层临时表的引擎都需要改,而导致 core 的代码只改了其中一层的临时表,没有把别的引擎一起改了,最后导致 core。

通过分析可以发现,问题代码判断阈值的公式也有问题,因此才导致一开始其中一层临时表申请内存超过阈值判断错误,修改了引擎类型。

这个问题涉及了 2 个原因,一环套一环,分析类似问题的时候,需要持续深挖本质原因,同时结合查询计划的执行和临时表创建情况分析,还需要对内存申请过程熟悉才能更好解决问题。

Enjoy GreatSQL :)

关于 GreatSQL

GreatSQL 是适用于金融级应用的国内自主开源数据库,具备高性能、高可靠、高易用性、高安全等多个核心特性,可以作为 MySQL 或 Percona Server 的可选替换,用于线上生产环境,且完全免费并兼容 MySQL 或 Percona Server。

http://www.dtcms.com/a/389578.html

相关文章:

  • 如何使用DeepSeek等AI工具来帮助自己的工作
  • 灵途科技亮相CIOE2025 | 光电感知赋能具身智能升级
  • 我的云端影院:LibreTV+cpolar的异地观影记
  • NW748NW765美光固态闪存NW775NW781
  • 软考中级习题与解答——第八章_计算机网络(1)
  • Playwright 完全指南:从入门到实战,解锁自动化测试新范式
  • OpenCV:直接用NV21/NV12格式,画线、贴图都是相加效果,而不是替换、覆盖
  • MCP3421与STM32电压采集实现
  • 表白网页制作免费网站制作 表白网站建设教程
  • 嵌入式Linux C语言程序设计一、二
  • cocos做简单自动发射追踪子弹 切换敌人
  • C#知识学习-014(修饰符_3)
  • Linux 下逆向解析 VNC Server 密码文件为明文密码(逆向解析passwd)
  • Linux dma_resv机制原理、实现与应用详解
  • LangGraph 进阶学习
  • Alibaba Cloud Linux与 RHEL/CentOS版本对应关系
  • Python实现PDF文本与表格转换
  • 医疗行业数字化转型:构建安全合规、高效协同的智慧医疗文档管理新范式
  • 怎么看一个网址是否安全?
  • 【LLM】RAG架构如何重塑大模型
  • 企业级数据库管理实战(四):从 C/S 到 B/S架构,数据库管理工具的演进
  • 基于AI的PDF复杂表格结构识别与智能解析(方案1)
  • CS336第三课
  • 云蝠智能大模型呼叫对话延迟无限接近1秒
  • Datax-web安装 | 配置环境
  • 算法<java>——查找(顺序、二分、插值、分块、斐波那契)
  • Mysql杂志(十九)——InnoDB的索引结构
  • CrowdStrike推出AI驱动新工具 聚焦补丁管理与威胁情报短板
  • 收集飞花令碎片——C语言指针
  • MySQL 初识:架构定位与整体组成