博客 / 詳情

返回

MySQL遊標執行帶有MINUS/INTERSECT查詢導致core問題解析

MySQL遊標執行帶有MINUS/INTERSECT查詢導致core問題解析

一、問題發現

在客户現場提交的一次問題中發現某個帶有MINUS聯合查詢cursor語句進行查詢的時候,用MINUS和INTERSECT進行聯合查詢會導致core,但是用UNION卻不會。

注意:這裏用的版本是debug版本會core,release版本會報錯。這個問題在MySQL 8.0.32版本會復現,最新的8.4.4版本關掉HASH_SET_OPERATIONS開關以後同樣復現。

看下面例子:

1、準備表和sp

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 ;

2、執行sp

執行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=<optimized out>) 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=<optimized out>) 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)

二、問題調查過程

1、8.0.32版本core問題調查

打開遊標的時候內部會創建臨時表用於保存結果數據,因此先看一下上面打開遊標的代碼執行流程:

img

從上面流程圖可以看出,執行查詢的時候臨時表進行了索引初始化,但是沒有進行關閉,接着在打開遊標的時候又進行了一次初始化,於是core了。

2、8.4.4版本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一樣,因此原因跟上圖一致。

3、總結問題

對比上面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;
}

修改之後的代碼調用流程如下:

img

上圖綠色部分為修復新增的代碼,當查詢結束的時候執行一次索引狀態重置,問題解決。

接着執行上面的查詢,發現可以查出結果了。

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 流出,造成後續的更多影響。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.