Skip to content

Commit 9895f51

Browse files
author
Olav Sandstaa
committed
Bug#21872184 CONDITIONAL JUMP AT JOIN_CACHE::WRITE_RECORD_DATA IN SQL_JOIN_BUFFER.CC
When a query using join buffering and one of the tables inserted into the join buffer was accessed using "dynamic range scan" on an index containing a virtual column, a valgrind error occurred when writing columns to the join buffer. Using the query from the added test case as example: SELECT t1.c1, t2.i1 FROM t1 STRAIGHT_JOIN t3 STRAIGHT_JOIN t2 WHERE ( t3.pk IN ( SELECT t4.i1 FROM t4 WHERE t4.c1 < 'o' ) ) AND t1.i1 <= t3.i2_key; This query joins three tables in the following order: t1 -> t3 -> t2 The access methods used are: t1: table scan t3: dynamic range scan t2: join buffering The table t3 is defined as follows: CREATE TABLE t3 ( pk INTEGER NOT NULL, i1 INTEGER, i2_key INTEGER GENERATED ALWAYS AS (i1 + i1) VIRTUAL NOT NULL, PRIMARY KEY (pk), KEY (i2_key) ); The issue arises when we write the (t1, t3) rows into the join buffer. The join buffer has been configured to include all columns marked in the read_set for t1 and t3. For the table t3, the read set contains (pk, i1, i2_key). Out of these, pk and i2_key are included in the read set due to they are needed for evaluating query conditions. i1 is included in the read set since it is needed for computing the value for the virtual column i2_key. This works fine when t3 is accessed using a table scan but for the rows where we switch to use "dynamic range scan", a valgrind error is reported. The valgrind error occurs when we want to copy the i1 field from the record buffer into the join cache buffer. Since this is a range scan on the i2_key index, the value for i1 has not been read into the record buffer (it is not needed). But it is still copied into the join buffer and the valgrind error happens when the code checks the null value for this field. The underlying cause for this problem, is that in order to support reading the table t3 from the storage engine as a table scan, we add the i1 field to the TABLE::read_set. When setting up join buffering for the query, we analyze which fields to include in the join buffer. This is done in JOIN_CACHE::filter_virtual_gcol_base_cols(). For table scans, all fields included in TABLE::read_set is copied to the join buffer. For access methods that read an index, only the fields included in the index is copied to the join buffer. When "dynamic range scan" is used, we do not know at the time when setting up the join buffer whether a table scan or an index read will be used (or which index that will be used). So in this case, all fields in the TABLE::read_set is set up to be copied to the join buffer. This patch extends JOIN_CACHE::filter_virtual_gcol_base_cols() to handle dynamic range scan. If any of the indexes that are candidates for the dynamic range scan is covering, then the table's read set is adjusted to only contain the columns that can be read by the alternative covering indexes.
1 parent 8cb7033 commit 9895f51

File tree

6 files changed

+971
-7
lines changed

6 files changed

+971
-7
lines changed

mysql-test/suite/gcol/inc/gcol_select.inc

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,8 @@ EXPLAIN SELECT * FROM t2 AS t1 WHERE b NOT IN (SELECT b FROM t1 FORCE INDEX(b));
828828
DROP TABLE t1;
829829
}
830830

831+
DROP TABLE t2, t3;
832+
831833
--echo #
832834
--echo # Bug#21317507:GC: STORED COLUMN REJECTED, BUT VIRTUAL IS ACCEPTED
833835
--echo #
@@ -1007,3 +1009,204 @@ DROP TABLE t0, t1;
10071009
# Restore defaults
10081010
set optimizer_switch= @optimizer_switch_save;
10091011
set @@read_rnd_buffer_size= @read_rnd_buffer_size_save;
1012+
1013+
--echo #
1014+
--echo # Bug#21872184 CONDITIONAL JUMP AT JOIN_CACHE::WRITE_RECORD_DATA IN
1015+
--echo # SQL_JOIN_BUFFER.CC
1016+
--echo #
1017+
1018+
--echo #
1019+
--echo # Test 1: Dynamic range scan with one covering index
1020+
--echo #
1021+
1022+
# This is the original test case which produces the valgrind error when
1023+
# inserting data into the join buffer. The test failure only occurs with
1024+
# InnoDB since it is only InnoDB that currently supports indexes on
1025+
# virtual columns and is the only storage engine that includes the
1026+
# primary key in each secondary key.
1027+
1028+
CREATE TABLE t1 (
1029+
i1 INTEGER NOT NULL,
1030+
c1 VARCHAR(1) NOT NULL
1031+
);
1032+
1033+
INSERT INTO t1
1034+
VALUES (10, 'c'), (10, 'i'), (2, 't'), (4, 'g');
1035+
1036+
CREATE TABLE t2 (
1037+
i1 INTEGER NOT NULL,
1038+
c1 VARCHAR(1) NOT NULL
1039+
);
1040+
1041+
INSERT INTO t2
1042+
VALUES (2, 'k'), (9, 'k'), (7, 'o'), (5, 'n'), (7, 'e');
1043+
1044+
CREATE TABLE t3 (
1045+
pk INTEGER NOT NULL,
1046+
i1 INTEGER,
1047+
i2_key INTEGER GENERATED ALWAYS AS (i1 + i1) VIRTUAL NOT NULL,
1048+
PRIMARY KEY (pk)
1049+
);
1050+
1051+
if ($support_virtual_index)
1052+
{
1053+
--echo # Add a covering index. The reason for this index being covering is that
1054+
--echo # secondary indexes in InnoDB include the primary key.
1055+
ALTER TABLE t3 ADD INDEX v_idx (i2_key);
1056+
}
1057+
1058+
INSERT INTO t3 (pk, i1)
1059+
VALUES (1, 1), (2, 48), (3, 228), (4, 3), (5, 5),
1060+
(6, 39), (7, 6), (8, 8), (9, 3);
1061+
1062+
CREATE TABLE t4 (
1063+
i1 INTEGER NOT NULL,
1064+
c1 VARCHAR(1) NOT NULL
1065+
);
1066+
1067+
INSERT INTO t4
1068+
VALUES (1, 'j'), (2, 'c'), (0, 'a');
1069+
1070+
# Hint is added to avoid materialization of the subquery
1071+
let query=
1072+
SELECT /*+ NO_SEMIJOIN(@subq1) */ t1.c1, t2.i1
1073+
FROM t1 STRAIGHT_JOIN t3 STRAIGHT_JOIN t2
1074+
WHERE ( t3.pk IN
1075+
(
1076+
SELECT /*+ QB_NAME(subq1) */ t4.i1
1077+
FROM t4
1078+
WHERE t4.c1 < 'o'
1079+
)
1080+
)
1081+
AND t1.i1 <= t3.i2_key;
1082+
1083+
eval EXPLAIN $query;
1084+
eval $query;
1085+
1086+
--echo #
1087+
--echo # Test 2: Two alternative covering indexes for the range scan
1088+
--echo #
1089+
1090+
# Adding second covering index
1091+
if ($support_virtual_index)
1092+
{
1093+
ALTER TABLE t3 ADD INDEX v_idx2 (i2_key, i1);
1094+
}
1095+
1096+
# Hint is added to avoid materialization of the subquery
1097+
let query=
1098+
SELECT /*+ NO_SEMIJOIN(@subq1) */ t1.c1, t2.i1
1099+
FROM t1 STRAIGHT_JOIN t3 STRAIGHT_JOIN t2
1100+
WHERE ( t3.pk IN
1101+
(
1102+
SELECT /*+ QB_NAME(subq1) */ t4.i1
1103+
FROM t4
1104+
WHERE t4.c1 < 'o'
1105+
)
1106+
)
1107+
AND t1.i1 <= t3.i2_key;
1108+
1109+
eval EXPLAIN $query;
1110+
eval $query;
1111+
1112+
--echo #
1113+
--echo # Test 3: One covering index including the base column for the virtual
1114+
--echo # column
1115+
--echo #
1116+
1117+
if ($support_virtual_index)
1118+
{
1119+
--echo # Drop the index with only the virtual column
1120+
ALTER TABLE t3 DROP INDEX v_idx;
1121+
}
1122+
1123+
# Hint is added to avoid materialization of the subquery
1124+
let query=
1125+
SELECT /*+ NO_SEMIJOIN(@subq1) */ t1.c1, t2.i1
1126+
FROM t1 STRAIGHT_JOIN t3 STRAIGHT_JOIN t2
1127+
WHERE ( t3.pk IN
1128+
(
1129+
SELECT /*+ QB_NAME(subq1) */ t4.i1
1130+
FROM t4
1131+
WHERE t4.c1 < 'o'
1132+
)
1133+
)
1134+
AND t1.i1 <= t3.i2_key;
1135+
1136+
eval EXPLAIN $query;
1137+
eval $query;
1138+
1139+
--echo #
1140+
--echo # Test 4: One non-covering index
1141+
--echo #
1142+
1143+
if ($support_virtual_index)
1144+
{
1145+
--echo # Drop the index on two columns, add index on just one virtual column
1146+
ALTER TABLE t3 DROP INDEX v_idx2;
1147+
ALTER TABLE t3 ADD INDEX v_idx (i2_key);
1148+
}
1149+
1150+
--echo # Add more data to the table so that it will run the dynamic range scan
1151+
--echo # as both table scan and range scan (the purpose of this is to make the
1152+
--echo # table scan more expensive).
1153+
INSERT INTO t3 (pk, i1)
1154+
VALUES (10,1), (11,1), (12,1), (13,1), (14,1),(15,1), (16,1),(17,1), (18,1),
1155+
(19,1), (20,1), (21,1), (22,1), (23,1), (24,1),(25,1),(26,1),(27,1),
1156+
(28,1), (29,1);
1157+
1158+
--echo # Change the query to read an extra column (t3.i1) making the index
1159+
--echo # non-covering.
1160+
# Hint is added to avoid materialization of the subquery
1161+
let query=
1162+
SELECT /*+ NO_SEMIJOIN(@subq1) */ t1.c1, t2.i1, t3.i1
1163+
FROM t1 STRAIGHT_JOIN t3 STRAIGHT_JOIN t2
1164+
WHERE ( t3.pk IN
1165+
(
1166+
SELECT /*+ QB_NAME(subq1) */ t4.i1
1167+
FROM t4
1168+
WHERE t4.c1 < 'o'
1169+
)
1170+
)
1171+
AND t1.i1 <= t3.i2_key;
1172+
1173+
eval EXPLAIN $query;
1174+
eval $query;
1175+
1176+
--echo #
1177+
--echo # Test 5: Test where the added primary key to secondary indexes is
1178+
--echo # used after it has been included in the join buffer
1179+
--echo #
1180+
1181+
# This test is only relevant for storage engines that add the primary key
1182+
# to all secondary keys (e.g. InnoDB). For these engines, the fields in the
1183+
# primary key might be included when deciding that a secondary index is
1184+
# covering for the query. This is the case for most of the secondary indexes
1185+
# on t3 in this test. But in the above queries, the subquery is non-dependent
1186+
# and the "t3.pk IN .." will be evaluated after rows for t3 are read. At this
1187+
# time t3.pk is in the record buffer. t3.pk is not used after it has been
1188+
# inserted into the join buffer. To test that t3.pk is actually correctly
1189+
# included in the join buffer we change the subquery to be dependent and
1190+
# only evaluated after the join has been done.
1191+
# The purpose of this test is to ensure that we correctly handle and
1192+
# include primary key fields that are added to a covering secondary index.
1193+
1194+
# The difference between this query and the query in test 1 is that
1195+
# an extra query condition is added to the subquery.
1196+
# Hint is added to avoid materialization of the subquery
1197+
let query=
1198+
SELECT /*+ NO_SEMIJOIN(@subq1) */ t1.c1, t2.i1
1199+
FROM t1 STRAIGHT_JOIN t3 STRAIGHT_JOIN t2
1200+
WHERE ( t3.pk IN
1201+
(
1202+
SELECT /*+ QB_NAME(subq1) */ t4.i1
1203+
FROM t4
1204+
WHERE t4.c1 < 'o' and t4.i1 < (t2.i1 + 1)
1205+
)
1206+
)
1207+
AND t1.i1 <= t3.i2_key;
1208+
1209+
eval EXPLAIN $query;
1210+
eval $query;
1211+
1212+
DROP TABLE t1, t2, t3, t4;

0 commit comments

Comments
 (0)