Bug #112557 | Inconsistent results of SELECT statement with EXISTS clause | ||
---|---|---|---|
Submitted: | 28 Sep 2023 7:40 | Modified: | 5 Aug 2024 16:17 |
Reporter: | Zuming Jiang | Email Updates: | |
Status: | Closed | Impact on me: | |
Category: | MySQL Server: DML | Severity: | S2 (Serious) |
Version: | 8.0.27,8.0.34 | OS: | Any |
Assigned to: | CPU Architecture: | Any | |
Tags: | regression |
[28 Sep 2023 7:40]
Zuming Jiang
[28 Sep 2023 9:34]
MySQL Verification Team
Hello Zuming Jiang, Thank you for the report and test case. regards, Umesh
[28 Sep 2023 9:37]
MySQL Verification Team
Looks like regression as 8.0.11 not affected ./mtr --nocheck-testcases bug112557 Logging: ./mtr --nocheck-testcases bug112557 2023-09-28T09:37:11.294492Z 0 [System] [MY-010116] [Server] /export/home/tmp/ushastry/mysql-8.0.11/bin/mysqld (mysqld 8.0.11) starting as process 9399 MySQL Version 8.0.11 Checking supported features... - SSL connections supported Collecting tests... Checking leftover processes... Removing old var directory... Creating var directory '/export/home/tmp/ushastry/mysql-8.0.11/mysql-test/var'... Installing system database... Using parallel: 1 ============================================================================== TEST RESULT TIME (ms) or COMMENT -------------------------------------------------------------------------- worker[1] Using MTR_BUILD_THREAD 300, with reserved ports 13000..13009 create table t1 (vkey int, pkey int, c5 int, c7 varchar(200)); insert into t1 values (20, 30000, 59, null); insert into t1 values (59, 69000, null, null); select ref_0.c5 >= (case when (true and exists ( select ref_2.pkey as c_3 from t1 as ref_2 where ref_2.vkey between ref_0.c5 and ref_0.c5 )) then null else (case when (FIRST_VALUE('') over (partition by ref_0.c7 order by ref_0.vkey asc, ref_0.pkey desc) >= null) then null else ref_0.pkey end ) end ) as c_6 from t1 as ref_0; c_6 NULL NULL select ref_0.c5 >= (case when (exists ( select ref_2.pkey as c_3 from t1 as ref_2 where ref_2.vkey between ref_0.c5 and ref_0.c5 )) then null else (case when (FIRST_VALUE('') over (partition by ref_0.c7 order by ref_0.vkey asc, ref_0.pkey desc) >= null) then null else ref_0.pkey end ) end ) as c_6 from t1 as ref_0; c_6 NULL NULL main.bug112557 [ pass ] 44 --------------------------------------------------------------------------
[31 Oct 2023 9:56]
huahua xu
Hi All, For Test Case 2, I tracked its execution in version 8.0.30: step 1: buffer all the rows in the same window. copy_funcs(m_temp_table_param, thd(), CFT_HAS_NO_WF) param->items_to_copy = 0x00000250d04cde98 {...} innodb(ref0.*) --> temptable(<temporary>.*) // copy the fields from the input table's buffer to the out table's fields save_in_field_no_error_check // execute the non-window functions which do not rely on window functions, and copy the result into the out table's fields buffer_windowing_record // buffer the current row to the frame buffer bool copy_fields(Temp_table_param *param, const THD *thd, bool reverse_copy) param->copy_fields = { size=5 } param 0x00000250d066bd60 {copy_fields={ size=5 } group_buff=0x0000000000000000 <NULL> items_to_copy=0x00000250d04ceea0 {...} ...} Temp_table_param * temptable(<temporary>.*) --> temptable(<>.*) // copy the fields from the out table's buffer to the window frame buffer buffer_record_somewhere t->file->ha_write_row(record) // save the window frame buffer to the frame buffer temporary table step 2: process the buffered rows by evaluating/aggregating the window functions defined over this window on the current frame. process_buffered_windowing_record bring_back_frame_row read_frame_buffer_row t->file->ha_rnd_pos // read row from the frame buffer temporary table bool copy_fields(Temp_table_param *param, const THD *thd, bool reverse_copy) param->copy_fields = { size=5 } param 0x00000250d066bd60 {copy_fields={ size=5 } group_buff=0x0000000000000000 <NULL> items_to_copy=0x00000250d04ceea0 {...} ...} Temp_table_param * temptable(<>.*) --> temptable(<temporary>.*) // copy the fields from the window frame buffer to the out table's buffer copy_funcs(param, thd, CFT_WF_NON_FRAMING) // execute the non framing window function, and copy the result into the out table's fields copy_funcs(param, thd, CFT_WF_FRAMING) // execute the framing window function, and copy the result into the out table's fields (the function Item_first_last_value `FIRST_VALUE('') over (partition by ref_0.c7 order by ref_0.vkey asc, ref_0.pkey desc)` would be executed) step 3: execute all non-wf functions, and prepare the output row. bring_back_frame_row read_frame_buffer_row t->file->ha_rnd_pos // read row from the frame buffer temporary table bool copy_fields(Temp_table_param *param, const THD *thd, bool reverse_copy) param->copy_fields = { size=5 } param 0x00000250d066bd60 {copy_fields={ size=5 } group_buff=0x0000000000000000 <NULL> items_to_copy=0x00000250d04ceea0 {...} ...} Temp_table_param * temptable(<>.*) --> temptable(<temporary>.*) // copy the fields from the window frame buffer to the out table's buffer copy_funcs(param, thd, CFT_HAS_WF) // execute the non-wf functions, and copy the result into the out table's fields (the function Item_func_ge `ref_0.c5 >= (case when ... )` would be executed) Obviously, the result could be incorrect when the function Item_func_ge `ref_0.c5 >= (case when ... )` is executed, because the values of its referenced fields comes from the last record in the input table for each window.
[31 Oct 2023 15:12]
Dag Wanvik
Posted by developer: Bisect points to this commit: 16054f827f57ffbd2e044141d5e5f0c1aaeb35af is the first bad commit commit 16054f827f57ffbd2e044141d5e5f0c1aaeb35af Author: Steinar H. Gunderson <steinar.gunderson@oracle.com> Date: Thu Apr 15 22:52:53 2021 +0200 Bug #32644631: PRELIMINARY FIXES FOR WL #14419 [wf framebuffer, noclose] Bug #32802301: REGRESSION: TYPE_CONVERSION_STATUS FIELD_VARSTRING::STORE: ASSERTION `!TABLE || (!TABLE->WRITE_SET || BITMAP_IS_SET(TABLE->WRITE_SET, FIELD_INDEX()))' FAILED.
[1 Nov 2023 13:12]
huahua xu
Hi Dag Wanvik, I agree with your opinion. For Test Case 2, I also tracked its execution in version 8.0.19: ``` step 1: buffer all the rows in the same window. copy_fields_and_funcs(m_temp_table_param, thd(), CFT_HAS_NO_WF) param 0x000002374c66e448 {grouped_expressions={ size=0 } copy_fields={ size=5 } group_buff=0x0000000000000000 <NULL> ...} Temp_table_param * bool copy_fields(Temp_table_param *param, const THD *thd, bool reverse_copy) param->copy_fields = { size=5 } innodb(ref0.*) --> temptable(<temporary>.*) // copy the fields from the input table's buffer to the out table's fields bool copy_funcs(Temp_table_param *param, const THD *thd, Copy_func_type type) param->items_to_copy // execute the non-window functions which do not rely on window functions, and copy the result into the out table's fields buffer_windowing_record // buffer the current row to the frame buffer param = 0x000002374c66d888 {grouped_expressions={ size=0 } copy_fields={ size=6 } group_buff=0x0000000000000000 <NULL> ...} bool copy_fields(Temp_table_param *param, const THD *thd, bool reverse_copy) param->copy_fields = { size=6 } innodb(ref0.*) --> temptable(<>.*) // copy the fields from the input table's buffer to the window frame buffer buffer_record_somewhere t->file->ha_write_row(record) // save the window frame buffer to the frame buffer temporary table step 2: process the buffered rows by evaluating/aggregating the window functions defined over this window on the current frame. process_buffered_windowing_record bool bring_back_frame_row(THD *thd, Window *w, Temp_table_param *out_param, ...) param 0x000002374c66d888 {grouped_expressions={ size=0 } copy_fields={ size=6 } group_buff=0x0000000000000000 <NULL> ...} Temp_table_param * read_frame_buffer_row t->file->ha_rnd_pos // read row from the frame buffer temporary table bool copy_fields(Temp_table_param *param, const THD *thd, bool reverse_copy) param->copy_fields = { size=6 } temptable(<>.*) --> innodb<ref0.*> // copy the fields from the window frame buffer to the input table's buffer bool copy_fields(Temp_table_param *param, const THD *thd, bool reverse_copy) param 0x000002374c66e448 {grouped_expressions={ size=0 } copy_fields={ size=5 } group_buff=0x0000000000000000 <NULL> ...} Temp_table_param * param->copy_fields = { size=5 } innodb(ref0.*) --> temptable(<temporary>.*) // copy the fields from the input table's buffer to the out table's fields copy_funcs(param, thd, CFT_WF_NON_FRAMING) // execute the non framing window function, and copy the result into the out table's fields copy_funcs(param, thd, CFT_WF_FRAMING) // execute the framing window function, and copy the result into the out table's fields (the function Item_first_last_value `FIRST_VALUE('') over (partition by ref_0.c7 order by ref_0.vkey asc, ref_0.pkey desc)` would be executed) step 3: execute all non-wf functions, and prepare the output row. bool bring_back_frame_row(THD *thd, Window *w, Temp_table_param *out_param, ...) read_frame_buffer_row t->file->ha_rnd_pos // read row from the frame buffer temporary table bool copy_fields(Temp_table_param *param, const THD *thd, bool reverse_copy) param 0x000002374c66d888 {grouped_expressions={ size=0 } copy_fields={ size=6 } group_buff=0x0000000000000000 <NULL> ...} Temp_table_param * param->copy_fields = { size=6 } temptable(<>.*) --> innodb(ref0.*) // copy the fields from the window frame buffer to the input table's buffer bool copy_fields(Temp_table_param *param, const THD *thd, bool reverse_copy) param 0x000002374c66e448 {grouped_expressions={ size=0 } copy_fields={ size=5 } group_buff=0x0000000000000000 <NULL> ...} Temp_table_param * param->copy_fields = { size=5 } innodb(ref0.*) --> temptable(<temporary>.*) // copy the fields from the input table's buffer to the out table's fields copy_funcs(param, thd, CFT_HAS_WF) // execute the non-wf functions, and copy the result into the out table's fields (the function Item_func_ge `ref_0.c5 >= (case when ... )` would be executed) ``` In the commit https://github.com/mysql/mysql-server/commit/1407269ba68f5a4f07431f6b6010d8aaf1b81510, a related modification is: ``` @@ -492,11 +491,11 @@ bool bring_back_frame_row(THD *thd, Window *w, Temp_table_param *out_param, if (!rc) { if (out_param) { if (copy_funcs(out_param, thd, CFT_FIELDS)) return true; // fields are in IN and in OUT // fields are in OUT if (rowno >= 1) w->set_row_has_fields_in_out_table(rowno); } else // we only wrote IN record, so OUT and IN are inconsistent // TODO(sgunders): Is this still needed? w->set_row_has_fields_in_out_table(0); } ```
[13 Dec 2023 11:53]
shan he
Could you please confirm whether the simplification process we applied has been helpful for developers in diagnosing and addressing this issue? Although not obvious for this example, it removes " partition by ref_0.c7 order by ref_0.vkey asc, ref_0.pkey desc) >= null". Here's our output ***** create table t1 (vkey int, pkey int, c5 int, c7 varchar(200)); insert into t1 values (20, 30000, 59, null); insert into t1 values (59, 69000, null, null); select (case when (true and exists ( select ref_2.pkey as c_3 from t1 as ref_2 where ref_2.vkey between ref_0.c5 and ref_0.c5 )) then null else (case when (FIRST_VALUE('') over () >= null) then null else ref_0.pkey end ) end ) as c_6 from t1 as ref_0; +-------+ | c_6 | +-------+ | NULL | | 69000 | +-------+ select (case when (exists ( select ref_2.pkey as c_3 from t1 as ref_2 where ref_2.vkey between ref_0.c5 and ref_0.c5 )) then null else (case when (FIRST_VALUE('') over () >= null) then null else ref_0.pkey end ) end ) as c_6 from t1 as ref_0; +-------+ | c_6 | +-------+ | 30000 | | 69000 | +-------+
[5 Aug 2024 16:17]
Jon Stephens
Documented fix as follows in the MySQL 9.1.0 changelog: Queries using CASE (WHEN TRUE AND EXISTS (/subquery/)) sometimes did not return the same result as with CASE (EXISTS (/subquery/)). Closed.