[MDEV-4840] Wrong result (missing rows) on LEFT JOIN with InnoDB tables Created: 2013-08-04 Updated: 2013-08-22 Resolved: 2013-08-22 |
|
| Status: | Closed |
| Project: | MariaDB Server |
| Component/s: | None |
| Affects Version/s: | 10.0.3, 5.5.32, 5.1.67, 5.2.14, 5.3.12 |
| Fix Version/s: | 10.0.5, 5.5.33 |
| Type: | Bug | Priority: | Major |
| Reporter: | Elena Stepanova | Assignee: | Sergei Petrunia |
| Resolution: | Fixed | Votes: | 0 |
| Labels: | None | ||
| Issue Links: |
|
||||
| Description |
|
With the test case below, the first query produces 3 rows, which I believe to be the correct result:
But the second query, which only differs from the previous one by the select list, produces two rows:
Reproducible on MariaDB 5.1 from the beginning of time (tried 5.1.42), 5.2, 5.3, 5.5, 10.0.
EXPLAIN:
|
| Comments |
| Comment by Oleksandr Byelkin [ 2013-08-05 ] | ||||||||||||||||||||
|
The problem is in removing LEFT JOIN... | ||||||||||||||||||||
| Comment by Igor Babaev [ 2013-08-05 ] | ||||||||||||||||||||
|
I investigated the bug. | ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-15 ] | ||||||||||||||||||||
|
Table elimination causes "city LEFT JOIN country ON ...) to be eliminated:
-----
----- | ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-15 ] | ||||||||||||||||||||
|
... and it is wrong to eliminate that join nest. Lets see what data it has: select * from city LEFT JOIN country ON city.country_code = country.code;
-------------
------------- we execute select ... from iso_code left join (...) on iso_code.alpha3 = country.code Apparently, the join nest may produce multiple records with the same | ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-15 ] | ||||||||||||||||||||
|
If one runs EXPLAIN EXTENDED SELECT * ..., they can see: ... from `j33`.`iso_code` left join (`j33`.`city` join `j33`.`country`) on ... That is , "city LEFT JOIN country" is converted into an inner join. This is not a problem per se. | ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-15 ] | ||||||||||||||||||||
|
Removing outer->inner join conversion from the consideration: MariaDB [j33]> SELECT iso_code.alpha3 FROM iso_code LEFT JOIN ( city JOIN country ON city.country_code = country.code ) ON iso_code.alpha3 = country.code;
--------
-------- MariaDB [j33]> set optimizer_switch='table_elimination=off'; MariaDB [j33]> SELECT iso_code.alpha3 FROM iso_code LEFT JOIN ( city JOIN country ON city.country_code = country.code ) ON iso_code.alpha3 = country.code;
--------
-------- | ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-15 ] | ||||||||||||||||||||
|
(gdb) step
(gdb) p dbug_print_item(cond) iso_code.alpha3 = country.code = city.country_code country.code is a PK. (gdb) fini
| ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-15 ] | ||||||||||||||||||||
|
It seems, the problem is here: (gdb) p this 358 void touch() { unbound_args--; }The code in Dep_module::touch() assumes that it is called from different sources. 1. table "country" has two unique keys. #2 should prevent Dep_module_goal from ever being marked as bound. However, #1 causes dep_module_goa->touch() be called twice, and the optimizer incorrectly assumes Dep_module_goal is satisfied. | ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-22 ] | ||||||||||||||||||||
|
This patch fixes the testcase for this bug: === modified file 'sql/opt_table_elimination.cc'
what I don't understand is why the testcase fails only with InnoDB. The problem that is fixed by this patch is orthogonal to the storage engine being used. | ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-22 ] | ||||||||||||||||||||
|
CREATE TABLE country ( code VARCHAR(3), name VARCHAR(64), PRIMARY KEY (code), UNIQUE KEY (name)) ENGINE=InnoDB; Breakpoint 14, Dep_value_field::get_next_unbound_module (this=0x7fff9400af08, dac=0x7fffc87c5e00, iter=0x7fffc87c5d00 "`\311\002\224\377\177") at /home/psergey/dev2/5.5/sql/opt_table_elimination.cc:1742 That is, country.code is considered to be covered by key#1 in table `country`. However, the constructor for this unique key assumes that the key as one key Breakpoint 13, Dep_module_key::Dep_module_key (this=0x7fff9402c960, table_arg=0x7fff9402c910, keyno_arg=1, n_parts_arg=1) at /home/psergey/dev2/5.5/sql/opt_table_elimination.cc:422 note above: keyno_arg=1, n_parts_arg=1 Somehow, extended-keys property is taken into account in one place but not in the other. | ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-22 ] | ||||||||||||||||||||
|
Dep_value_table *Dep_analysis_context::create_table_value(TABLE *table) has this code if (key->flags & HA_NOSAME) As for key->key_parts: (gdb) p *key It seems, usable_key_parts should be used instead? | ||||||||||||||||||||
| Comment by Sergei Petrunia [ 2013-08-22 ] | ||||||||||||||||||||
|
Handling extended keys in table elimination. Table elimination uses primary/unique key definitions to know which set of Extending the binding column set makes things worse for table elimination. UNIQUE KEY(col1), and is able to infer that "col1=...." makes the table bound. With extended keys, table elimination sees: UNIQUE KEY(col1, pk_col) and this doesn't allow to infer that "col1=..." makes the table bound. |