|
在客户现场提交的一次问题中发现某个带有 MINUS 联合查询 cursor 语句进行查询的时候,用 MINUS 和 INTERSECT 进行联合查询会导致 core,但是用 UNION 却不会。
注意:这里用的版本是 debug 版本会 core,release 版本会报错。这个问题在 MySQL 8.0.32 版本会复现,最新的 8.4.4 版本关掉 HASH_SET_OPERATIONS 开关以后同样复现。
看下面例子:
8032版本执行以下命令:
CREATE TABLE t1 (a INT, b VARCHAR(3));
INSERT INTO t1 values(1,'aa'),(2,'bb'),(3,'cc'),(6,'ee') ;
CREATE TABLE t2 (a INT, b VARCHAR(3));
INSERT INTO t2 values(1,'aa'),(4,'bb'),(3,'cc'),(5,'dd') ;
SET sql_mode=oracle;
DELIMITER $$
CREATE or replace PROCEDURE p1()
IS
BEGIN
FOR v IN(
SELECT * FROM t1
minus
SELECT * FROM t2
) LOOP
SELECT v.a ;
END LOOP;
END;$$
DELIMITER ;
执行 sp 可以看到 core 了。
-- CALL p1; 结果core了
core堆栈如下:
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007ffff6a068e4 in __GI_abort () at abort.c:79
#2 0x00007ffff6a067cf in __assert_fail_base (
fmt=0x7ffff6b60e90 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n",
assertion=0x6073198 "inited NONE || (inited RND && scan)",
file=0x6071b68 "sql/handler.cc",
line=3072, function=) at assert.c:92
#3 0x00007ffff6a13f02 in GI_assert_fail (
assertion=0x6073198 "inited NONE || (inited RND && scan)",
file=0x6071b68 "sql/handler.cc",
line=3072, function=0x6073178 "int handler::ha_rnd_init(bool)")
at assert.c:101
#4 0x00000000034fb3e1 in handler::ha_rnd_init (this=0x7fff2c9ab490,
scan=true) at sql/handler.cc:3072
#5 0x0000000003a339e0 in Materialized_cursor::open (
this=0x7fff2c88bff8, thd=0x7fff2c001010)
at sql/sql_cursor.cc:375
#6 0x0000000003a333e5 in mysql_open_cursor (thd=0x7fff2c001010, result=
0x7fff2c604ac8, pcursor=0x7fff2c604ab8)
at sql/sql_cursor.cc:280
#7 0x00000000039ad4dc in sp_cursor::open (this=0x7fff2c604ab0,
thd=0x7fff2c001010)
at sql/sp_rcontext.cc:1262
#8 0x0000000003997f6e in sp_instr_cpush_rowtype::exec_core (
this=0x7fff2c881560, thd=0x7fff2c001010)
at sql/sp_instr.cc:1986
#9 0x0000000003993ae5 in sp_lex_instr::reset_lex_and_exec_core (
this=0x7fff2c881560, thd=0x7fff2c001010, nextp=0x7fffd45f2998,
open_tables=false)
at sql/sp_instr.cc:462
#10 0x000000000399472a in sp_lex_instr::validate_lex_and_execute_core (
this=0x7fff2c881560, thd=0x7fff2c001010, nextp=0x7fffd45f2998,
open_tables=false)
at sql/sp_instr.cc:769
#11 0x0000000003998f89 in sp_instr_copen::execute (this=0x7fff2c881a88,
thd=0x7fff2c001010, nextp=0x7fffd45f2998)
at sql/sp_instr.cc:2282
(gdb) f 4
#4 0x00000000034fb3e1 in handler::ha_rnd_init (this=0x7fff2c9ab490,
scan=true) at sql/handler.cc:3072
3072 assert(inited NONE || (inited RND && scan));
(gdb) p inited 这里引擎变为索引了,说明在前面的过程里引擎的索引没有执行HA_INDEX_END
$1 = handler::INDEX
3、8.4.4 版本执行 sp
8.4.4 版本的 HASH_SET_OPERATIONS 开关默认开启的,因此这里不需要设置。
# 首先创建正常sp。
SET sql_mode=oracle;
DELIMITER $$
CREATE or replace PROCEDURE p1()
IS
BEGIN
FOR v IN(
SELECT * FROM t1
minus
SELECT * FROM t2
) LOOP
SELECT v.a ;
END LOOP;
END;$$
DELIMITER ;
# 接着执行这个sp,发现有结果,符合预期。
greatsql> CALL p1;
+------+
| v.a |
+------+
| 2 |
+------+
1 row in set (0.01 sec)
+------+
| v.a |
+------+
| 6 |
+------+
1 row in set (0.01 sec)
没问题是不是说明 bug 解决了呢?现在关掉 HASH_SET_OPERATIONS 开关,再次创建这个 sp 再运行一次。可以看到结果 core 了,说明这个 bug 并没有解决。
SET sql_mode=oracle;
DELIMITER $$
CREATE OR replace PROCEDURE p1()
IS
BEGIN
FOR v IN(
SELECT /*+ set_var(optimizer_switch='HASH_SET_OPERATIONS=off') */ * FROM t1
minus
SELECT * from t2
) LOOP
SELECT v.a ;
END LOOP;
END;$$
DELIMITER ;
CALL p1; # 这里core了
堆栈如下,可以发现跟8032版本的堆栈完全一样:
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007ffff5bba8e4 in __GI_abort () at abort.c:79
#2 0x00007ffff5bba7cf in __assert_fail_base (
fmt=0x7ffff5d14e90 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n",
assertion=0x62be3b8 "inited NONE || (inited RND && scan)",
file=0x62bce28 "sql/handler.cc",
line=3151, function=) at assert.c:92
#3 0x00007ffff5bc7f02 in GI_assert_fail (
assertion=0x62be3b8 "inited NONE || (inited RND && scan)",
file=0x62bce28 "sql/handler.cc",
line=3151, function=0x62be398 "int handler::ha_rnd_init(bool)")
at assert.c:101
#4 0x000000000358db35 in handler::ha_rnd_init (this=0x7fff34047850, scan=true)
at sql/handler.cc:3151
#5 0x0000000003b0f0b2 in Materialized_cursor::open (this=0x7fff341017c8,
thd=0x7fff34000ec0)
at sql/sql_cursor.cc:381
#6 0x0000000003b0eab7 in mysql_open_cursor (thd=0x7fff34000ec0,
result=0x7fff340d4248, pcursor=0x7fff340d4238)
at sql/sql_cursor.cc:286
#7 0x0000000003a7a6ee in sp_cursor::open (this=0x7fff340d4230,
thd=0x7fff34000ec0)
打开游标的时候内部会创建临时表用于保存结果数据,因此先看一下上面打开游标的代码执行流程:
从上面流程图可以看出,执行查询的时候临时表进行了索引初始化,但是没有进行关闭,接着在打开游标的时候又进行了一次初始化,于是 core 了。
8.4.4 版本要分 2 个场景讨论,首先第一个不 core 的场景,也就是 optimizer_switch='HASH_SET_OPERATIONS=on' 的场景,从代码看跟 8032 版本不同处在于 Query_result_materialize::start_execution 的时候,table->share->keys 数量等于 0,而 8032 版本这个地方的 keys 数量等于 1。因此在 8.4.4 版本 table->file->ha_index_init 的时候 inited 没有设置为 INDEX 而是保持为 NONE,后面打开游标的时候初始化不会 core。
bool instantiate_tmp_table(THD thd, TABLE table) {
// Ensure that "in_use" is synchronized with the current session
assert(table->in_use nullptr || table->in_use thd);
table->in_use = thd;
TABLE_SHARE *const share = table->s;
// 跟8032代码相比多了这一行,这里把keys值设为0,因此后面临时表不创建索引,也就不会导致打开cursor的core。
if (table->uses_hash_map()) share->keys = 0;
而 optimizer_switch='HASH_SET_OPERATIONS=off' 的时候,代码流程跟 8032 一样,因此原因跟上图一致。
对比上面 1 和 2 可以发现,8.4.4 版本开启 HASH_SET_OPERATIONS 开关只是规避了问题,并没有解决问题。因此这个导致 core 的问题始终存在。
结合上面分析,我们可以在第一次 table->file->ha_index_init 执行之后到结束的时候调用 ha_index_end 就可以了,这样接下来打开游标的时候引擎状态就是 NONE,就不会 core 了。
添加如下代码,就可以解决这个问题了。
bool Query_result_materialize::send_eof(THD *) {
bool rc = false;
if (table->hash_field && table->file->inited == handler::INDEX)
rc = table->file->ha_index_end();
return rc;
}
修改之后的代码调用流程如下:
上图绿色部分为修复新增的代码,当查询结束的时候执行一次索引状态重置,问题解决。
接着执行上面的查询,发现可以查出结果了。
greatsql> call p1;
+------+
| v.a |
+------+
| 2 |
+------+
1 row in set (0.01 sec)
+------+
| v.a |
+------+
| 6 |
+------+
1 row in set (0.01 sec)
通过以上分析我们可以发现,执行带有 MINUS 和 INTERSECT 联合查询的 cursor 的时候,游标储存结果的临时表的索引状态会多次改变,如果索引状态的开启和结束没有配套设置的话,会影响后面 cursor 的打开。同时,不同版本的 MySQL 会有不同情况,像本次例子中,HASH_SET_OPERATIONS 开关也会对结果有影响。这就需要研发人员耐心多看代码,多尝试不同情况的查询 SQL 来分析问题,而不是看到某一种场景没问题了以为 BUG 修复了,那样会导致潜在 BUG 流出,造成后续的更多影响。
合作电话:010-64087828
社区邮箱:greatsql@greatdb.com


