Skip to content

Commit ef32cb4

Browse files
author
Jan Wedvik
committed
Bug#35703114 Assertion `m_psi_batch_mode == PSI_BATCH_MODE_NONE' failed.
The fix for bug#34940000 "Hash join execution may be inefficient if probe input is empty" caused a regression. An assert failure occurs under the following circumstances: - We execute a hash join where we start reading from the probe input e.g. a left join. - There is an error when reading that first row, e.g. because there is a filter on the probe input where we get a string conversion error. In this situation, we stop executing the join, but do not turn off PFS batch mode for the probe iterator. (PFS batch mode is an optimization for queries involving the performance schema.) This triggers the assert. This commit fixes this bug by ensuring that we stop PFS batch mode in this situation. Change-Id: I49d03d1896f3cff7aa01058e194be2afb89a0f81
1 parent e600779 commit ef32cb4

File tree

5 files changed

+159
-57
lines changed

5 files changed

+159
-57
lines changed

mysql-test/include/hash_join.inc

+30
Original file line numberDiff line numberDiff line change
@@ -2713,3 +2713,33 @@ EXPLAIN ANALYZE FORMAT=tree SELECT * FROM t1 x1 WHERE x1.a < 0 AND x1.a NOT IN
27132713
(SELECT a FROM t1 x2 WHERE x2.b = x1.b);
27142714

27152715
DROP TABLE t1;
2716+
2717+
--echo #
2718+
--echo # Bug#35703114 Assertion `m_psi_batch_mode == PSI_BATCH_MODE_NONE'
2719+
--echo # failed.
2720+
--echo #
2721+
2722+
CREATE TABLE t1(i1 int primary key, i2 int, c varchar(10));
2723+
2724+
INSERT INTO t1 VALUES (1,1,'a'), (2,2,'a'), (3,3,'a'), (4,4,'a'), (5,5,'a');
2725+
2726+
ANALYZE TABLE t1;
2727+
2728+
# To reproduce the bug we need a query with these properties:
2729+
# - It should be planned as a hash join.
2730+
# - We should start reading from the probe input (i.e. outer input).
2731+
# Therefore we use a left join.
2732+
# - There should be a filter on the probe input.
2733+
# - Evaluating this filter should give an error for the first row.
2734+
# Therefore we use an invalid Unicode code point.
2735+
let $query =
2736+
SELECT * FROM t1 x1 LEFT JOIN t1 x2 ON x1.i2=x2.i2
2737+
WHERE x1.c <= CHAR(0xd83f);
2738+
2739+
--replace_regex $elide_costs_and_rows
2740+
--eval EXPLAIN FORMAT=TREE $query
2741+
2742+
--error ER_CANNOT_CONVERT_STRING
2743+
--eval $query
2744+
2745+
DROP TABLE t1;

mysql-test/r/hash_join.result

+22
Original file line numberDiff line numberDiff line change
@@ -4307,3 +4307,25 @@ EXPLAIN
43074307
Warnings:
43084308
Note 1276 Field or reference 'test.x1.b' of SELECT #2 was resolved in SELECT #1
43094309
DROP TABLE t1;
4310+
#
4311+
# Bug#35703114 Assertion `m_psi_batch_mode == PSI_BATCH_MODE_NONE'
4312+
# failed.
4313+
#
4314+
CREATE TABLE t1(i1 int primary key, i2 int, c varchar(10));
4315+
INSERT INTO t1 VALUES (1,1,'a'), (2,2,'a'), (3,3,'a'), (4,4,'a'), (5,5,'a');
4316+
ANALYZE TABLE t1;
4317+
Table Op Msg_type Msg_text
4318+
test.t1 analyze status OK
4319+
EXPLAIN FORMAT=TREE SELECT * FROM t1 x1 LEFT JOIN t1 x2 ON x1.i2=x2.i2
4320+
WHERE x1.c <= CHAR(0xd83f);
4321+
EXPLAIN
4322+
-> Left hash join (x2.i2 = x1.i2) (...)
4323+
-> Filter: (x1.c <= <cache>(char(0xd83f))) (...)
4324+
-> Table scan on x1 (...)
4325+
-> Hash
4326+
-> Table scan on x2 (...)
4327+
4328+
SELECT * FROM t1 x1 LEFT JOIN t1 x2 ON x1.i2=x2.i2
4329+
WHERE x1.c <= CHAR(0xd83f);
4330+
ERROR HY000: Cannot convert string '\xD8?' from binary to utf8mb4
4331+
DROP TABLE t1;

mysql-test/r/hash_join_hypergraph.result

+22
Original file line numberDiff line numberDiff line change
@@ -4272,6 +4272,28 @@ Warnings:
42724272
Note 1276 Field or reference 'test.x1.b' of SELECT #2 was resolved in SELECT #1
42734273
DROP TABLE t1;
42744274
#
4275+
# Bug#35703114 Assertion `m_psi_batch_mode == PSI_BATCH_MODE_NONE'
4276+
# failed.
4277+
#
4278+
CREATE TABLE t1(i1 int primary key, i2 int, c varchar(10));
4279+
INSERT INTO t1 VALUES (1,1,'a'), (2,2,'a'), (3,3,'a'), (4,4,'a'), (5,5,'a');
4280+
ANALYZE TABLE t1;
4281+
Table Op Msg_type Msg_text
4282+
test.t1 analyze status OK
4283+
EXPLAIN FORMAT=TREE SELECT * FROM t1 x1 LEFT JOIN t1 x2 ON x1.i2=x2.i2
4284+
WHERE x1.c <= CHAR(0xd83f);
4285+
EXPLAIN
4286+
-> Left hash join (x1.i2 = x2.i2) (...)
4287+
-> Filter: (x1.c <= <cache>(char(0xd83f))) (...)
4288+
-> Table scan on x1 (...)
4289+
-> Hash
4290+
-> Table scan on x2 (...)
4291+
4292+
SELECT * FROM t1 x1 LEFT JOIN t1 x2 ON x1.i2=x2.i2
4293+
WHERE x1.c <= CHAR(0xd83f);
4294+
ERROR HY000: Cannot convert string '\xD8?' from binary to utf8mb4
4295+
DROP TABLE t1;
4296+
#
42754297
# Bug#34940000 Hash join execution may be ineficcient if probe input is empty
42764298
#
42774299
CREATE TABLE t1(

sql/iterators/hash_join_iterator.cc

+75-57
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,62 @@ bool HashJoinIterator::InitProbeIterator() {
153153
return false;
154154
}
155155

156+
bool HashJoinIterator::ReadFirstProbeRow() {
157+
assert(m_first_input == HashJoinInput::kProbe);
158+
m_state = State::READING_ROW_FROM_PROBE_ITERATOR;
159+
160+
if (InitProbeIterator()) {
161+
return true;
162+
}
163+
164+
const int result = m_probe_input->Read();
165+
if (result == 1) {
166+
return true;
167+
168+
} else if (result == -1) {
169+
m_state = State::END_OF_ROWS;
170+
return false;
171+
172+
} else {
173+
assert(result == 0);
174+
m_probe_row_read = true;
175+
// Prepare to read the build input into the hash map.
176+
PrepareForRequestRowId(m_build_input_tables.tables(),
177+
m_tables_to_get_rowid_for);
178+
179+
return false;
180+
}
181+
}
182+
183+
bool HashJoinIterator::InitHashTable() {
184+
if (BuildHashTable()) {
185+
return true;
186+
}
187+
188+
if (m_hash_table_generation != nullptr) {
189+
m_last_hash_table_generation = *m_hash_table_generation;
190+
}
191+
192+
if (m_state == State::END_OF_ROWS) {
193+
// BuildHashTable() decided that the join is done (the build input is
194+
// empty, and we are in an inner-/semijoin. Anti-/outer join must output
195+
// NULL-complemented rows from the probe input).
196+
return false;
197+
}
198+
199+
if (m_join_type == JoinType::ANTI && m_join_conditions.empty() &&
200+
m_extra_condition == nullptr && !m_row_buffer.empty()) {
201+
// For degenerate antijoins, we know we will never output anything
202+
// if there's anything in the hash table, so we can end right away.
203+
// (We also don't need to read more than one row, but
204+
// CreateHashJoinAccessPath() has already added a LIMIT 1 for us
205+
// in this case.)
206+
m_state = State::END_OF_ROWS;
207+
}
208+
209+
return false;
210+
}
211+
156212
bool HashJoinIterator::Init() {
157213
// If we are entirely in-memory and the JOIN we are part of hasn't been
158214
// asked to clear its hash tables since last time, we can reuse the table
@@ -243,73 +299,35 @@ bool HashJoinIterator::Init() {
243299
m_tables_to_get_rowid_for);
244300

245301
if (m_first_input == HashJoinInput::kProbe) {
246-
m_state = State::READING_ROW_FROM_PROBE_ITERATOR;
247-
248-
if (InitProbeIterator()) {
249-
return true;
250-
}
251-
252-
const int result = m_probe_input->Read();
253-
if (result == 1) {
254-
assert(thd()->is_error() ||
255-
thd()->killed); // my_error should have been called.
256-
return true;
257-
} else if (result == -1) {
258-
m_probe_input->EndPSIBatchModeIfStarted();
259-
m_state = State::END_OF_ROWS;
260-
return false;
261-
} else {
262-
assert(result == 0);
263-
m_probe_row_read = true;
264-
// Prepare to read the build input into the hash map.
265-
PrepareForRequestRowId(m_build_input_tables.tables(),
266-
m_tables_to_get_rowid_for);
267-
if (m_build_input->Init()) {
268-
assert(thd()->is_error() ||
269-
thd()->killed); // my_error should have been called.
302+
const bool error = [&]() {
303+
if (ReadFirstProbeRow()) {
270304
return true;
305+
} else if (m_state == State::END_OF_ROWS) {
306+
return false;
307+
} else {
308+
return m_build_input->Init() || InitHashTable();
271309
}
272-
}
273-
}
310+
}();
274311

275-
// Build the hash table
276-
if (BuildHashTable()) {
277-
assert(thd()->is_error() ||
312+
assert(!error || thd()->is_error() ||
278313
thd()->killed); // my_error should have been called.
279-
if (m_first_input == HashJoinInput::kProbe) {
280-
m_probe_input->EndPSIBatchModeIfStarted();
281-
}
282-
return true;
283-
}
284-
if (m_hash_table_generation != nullptr) {
285-
m_last_hash_table_generation = *m_hash_table_generation;
286-
}
287314

288-
if (m_state == State::END_OF_ROWS) {
289-
// BuildHashTable() decided that the join is done (the build input is
290-
// empty, and we are in an inner-/semijoin. Anti-/outer join must output
291-
// NULL-complemented rows from the probe input).
292-
if (m_first_input == HashJoinInput::kProbe) {
315+
if (m_state == State::END_OF_ROWS || error) {
293316
m_probe_input->EndPSIBatchModeIfStarted();
294317
}
295-
return false;
296-
}
297318

298-
if (m_join_type == JoinType::ANTI && m_join_conditions.empty() &&
299-
m_extra_condition == nullptr && !m_row_buffer.empty()) {
300-
// For degenerate antijoins, we know we will never output anything
301-
// if there's anything in the hash table, so we can end right away.
302-
// (We also don't need to read more than one row, but
303-
// CreateHashJoinAccessPath() has already added a LIMIT 1 for us
304-
// in this case.)
305-
if (m_first_input == HashJoinInput::kProbe) {
306-
m_probe_input->EndPSIBatchModeIfStarted();
319+
return error;
320+
321+
} else { // Start with 'build' input.
322+
if (InitHashTable()) {
323+
assert(thd()->is_error() ||
324+
thd()->killed); // my_error should have been called.
325+
326+
return true;
307327
}
308-
m_state = State::END_OF_ROWS;
309-
return false;
310-
}
311328

312-
return m_first_input == HashJoinInput::kProbe ? false : InitProbeIterator();
329+
return m_state == State::END_OF_ROWS ? false : InitProbeIterator();
330+
}
313331
}
314332

315333
// Construct a join key from a list of join conditions, where the join key from

sql/iterators/hash_join_iterator.h

+10
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,16 @@ class HashJoinIterator final : public RowIterator {
677677
/// that input was empty. If so, we should process that row before reading
678678
/// another.
679679
bool m_probe_row_read{false};
680+
681+
/// Helper function for Init(). Read the first row from m_probe_input.
682+
/// @returns 'true' if there was an error.
683+
bool ReadFirstProbeRow();
684+
685+
/// Helper function for Init(). Build the hash table and check for empty query
686+
/// results (empty build input or non-empty build input in case of degenerate
687+
/// antijoin.)
688+
/// @returns 'true' in case of error.
689+
bool InitHashTable();
680690
};
681691

682692
#endif // SQL_ITERATORS_HASH_JOIN_ITERATOR_H_

0 commit comments

Comments
 (0)