Bug #59186 | Wrong results of join when ICP is enabled | ||
---|---|---|---|
Submitted: | 27 Dec 2010 15:09 | Modified: | 3 Mar 2011 1:32 |
Reporter: | Valeriy Kravchuk | Email Updates: | |
Status: | Closed | Impact on me: | |
Category: | MySQL Server: Optimizer | Severity: | S2 (Serious) |
Version: | 5.6.2-m5 | OS: | Any |
Assigned to: | Olav Sandstå | CPU Architecture: | Any |
Tags: | index_condition_pushdown, optimizer_switch, regression |
[27 Dec 2010 15:09]
Valeriy Kravchuk
[27 Dec 2010 15:11]
Valeriy Kravchuk
Verified with current mysql-trunk from bzr on Mac OS X. Workaround is to switch ICP off for this kind of queries.
[12 Jan 2011 9:43]
Olav Sandstå
The index conditions that are pushed down to the storage engine are: For table "t1" on the primary key ("f3"): ((`test`.`t1`.`f3` in (2,1)) or ((`test`.`t1`.`f3` > 1) and (`test`.`t1`.`f3` < 5))) For table "t2" on the primary key ("f3"): ((`test`.`t2`.`f3` and (`test`.`t2`.`f3` <= 4)) or (`test`.`t2`.`f3` between 6 and 6))
[20 Jan 2011 10:33]
Olav Sandstå
Simplified version of test case: ================================ CREATE TABLE t1 ( pk INTEGER NOT NULL, c1 VARCHAR(3) NOT NULL, PRIMARY KEY (pk) ); INSERT INTO t1 VALUES (1,'y'),(0,'or'); CREATE TABLE t2 ( pk INTEGER NOT NULL, c1 VARCHAR(3) NOT NULL, c2 VARCHAR(6) NOT NULL, PRIMARY KEY (pk) ); INSERT INTO t2 VALUES (6,'y','RPOYT'),(10,'m','JINQE'); SELECT c2 FROM t1 JOIN t2 ON t1.c1 = t2.c1 WHERE (t2.pk <= 4 AND t1.pk IN (2,1)) OR (t1.pk > 1 AND t2.pk BETWEEN 6 AND 6); DROP TABLE t1, t2;
[20 Jan 2011 12:17]
Olav Sandstå
Investigation of why this query produces the wrong result: ========================================================== This problem does only appear when index condition pushdown is enabled. According to explain, index condition pushdown is used for both tables. 1. Index condition pushdown for table "t1" on the primary key: ============================================================== At the end of the optimization phase table "t1" has the following select condition: ((t1.pk in (2,1)) or (t1.pk > 1)) Since ICP is enabled the optimizer will attempt to push down as much as possible of this condition to the storage engine. Most of the logic for determining which part of the condition that can be pushed down is found in make_cond_for_index(). This function will recursively traverse the condition tree and for each node determine which parts of the tree that can be pushed down to the storage engine. In this case it will evaluate the following sub trees: (t1.pk in (2,1) (t1.pk > 1) ((t1.pk in (2,1)) or (t1.pk > 1)) In this case all of these are found to be safe to push down to the storage engine and the complete condition is then pushed down. One important detail to note here is that during this evaluation all nodes that are considered safe to push down are "marked" using the following member of Item: cond->marker= ICP_COND_USES_INDEX_ONLY; (so in this case all the above nodes in the tree get this "marker"). After the condition has been pushed down to the storage engine (and accepted by the storage engine) we compute the "remainder" for the condition, ie. the part of the complete condition that must be evaluated by the server. This "remainder" condition is created by the function make_cond_remainder() which traverses the complete condition and builds a new condition tree that only contains the parts that have not been pushed down to the storage engine. To avoid that this function has to do the entire evaluation for which parts that can be pushed down it checks the cond->marker member. If this has the value ICP_COND_USES_INDEX_ONLY it can skip this part of the tree. So in this case it only has to look at the root node of the original condition and concludes that everything is pushed down and that there is nothing left that the server needs to evaluate. 2. Index condition pushdown for table "t2" on the primary key: ============================================================== At the end of the optimization phase table "t2" has the following select condition: ((t2.c1 = t1.c1) and (((t2.pk <= 4) and (t1.pk in (2,1))) or ((t1.pk > 1) and (t2.pk between 6 and 6)))) make_cond_for_index() produces the following condition that can be pushed down to the storage engine: ((t2.pk <= 4) or (t2.pk between 6 and 6)) So far everything is "seems" to be correct. After this condition is pushed down we find the "remainder". In this case make_cond_remainder() computes the following: t2.c1 = t1.c1 as the "remainder". This is assigned as "t2"'s select condition and will be evaluated by the server. This is NOT correct as there are now parts of the original select condition that do not get evaluated neither by the storage engine nor the server. This causes the extra row to appear in the result from the query. So why do make_cond_remainder() fail in this case? It will traverse the original select condition and include nodes that have not been marked with ICP_COND_USES_INDEX_ONLY. When make_cond_for_index() was running only the two nodes that got pushed down to the storage engine should have been marked: (t2.pk <= 4) (t2.pk between 6 and 6)) but when make_cond_remainder is running the entire sub tree: ((t2.pk <= 4) and (t1.pk in (2,1))) or ((t1.pk > 1) and (t2.pk between 6 and 6))) is already marked with ICP_COND_USES_INDEX_ONLY (and thus not included in the "remainder"). And looking at the leaf nodes, all of them are marked with ICP_COND_USES_INDEX_ONLY: (t2.pk <= 4), (t1.pk in (2,1)), (t1.pk > 1), (t2.pk between 6 and 6) And the cause for this is: The two leaf nodes that should not have been marked with ICP_COND_USES_INDEX_ONLY are shared between the select condition for "t1" and "t2" and is stored in the same Item object. These two were marked with ICP_COND_USES_INDEX_ONLY when running make_cond_for_index() on table "t1". The marker value is not cleared before or during running make_cond_for_index() on "t2" and thus these will still have the ICP_COND_USES_INDEX_ONLY marker when running make_cond_remainder() for table "t2". This causes them to not be included in the select condition to be evaluated by the server for table "t2".
[20 Jan 2011 22:32]
Bugs System
A patch for this bug has been committed. After review, it may be pushed to the relevant source trees for release in the next version. You can access the patch from: http://lists.mysql.com/commits/129298 3539 Olav Sandstaa 2011-01-20 Fix for Bug#59186 Wrong results of join when ICP is enabled When index condition pushdown was used an extra row appeared in the result set due to parts of the select condition for the second table in the JOIN was not evaluated. This was caused when computing the "remainder" for the second table's select condition after pushing down parts of it to the storage engine. If parts of the select condition was common for both tables in the JOIN and the common part was pushed down for the first table the common part could have the marker field set to ICP_COND_USES_INDEX_ONLY during evaluation of make_cond_for_index() for the first table. If the common part of the select condition was not pushed down for the second table it would still be marked with ICP_COND_USES_INDEX_ONLY when computing the remainder for the select condition for the second table. This would cause that this part of the select condition neither was pushed down to the storage engine nor included in the select condition to be evaluated by the server. The fix for this is to extend make_cond_for_index() so that it clears the marker field for the parts of the item tree that it decides should not be pushed down to the storage engine. This will prevent that common items in select conditions for different tables that have been marked with ICP_COND_USES_INDEX_ONLY when evaluating one table does not keep this value when computing the "remainder" for a following table in a JOIN. @ mysql-test/include/icp_tests.inc Test case for Bug#59186 Wrong results of join when ICP is enabled. @ mysql-test/r/innodb_icp.result Result file for the test for Bug#59186 Wrong results of join when ICP is enabled. @ mysql-test/r/innodb_icp_none.result Result file for the test for Bug#59186 Wrong results of join when ICP is enabled. @ mysql-test/r/myisam_icp.result Result file for the test for Bug#59186 Wrong results of join when ICP is enabled. @ mysql-test/r/myisam_icp_none.result Result file for the test for Bug#59186 Wrong results of join when ICP is enabled. @ sql/sql_select.cc Reset the condition's marker field in make_cond_for_index() when it has determined that this part of the select condition should not be included in the condition to be pushed down to the storage engine. The reason this must be cleared is that if the condition is a common part of for the select condition of two tables in a JOIN operation then the marker field might have gotten the value set to ICP_COND_USES_INDEX_ONLY when evaluating the select condition for the first table. If this is the case we need to reset it to avoid that this part of the select condition is wrongly concluded to not be needed in the select condition to be evaluated by the server after pushing parts of the condition down to the storage engine.
[28 Jan 2011 11:19]
Bugs System
A patch for this bug has been committed. After review, it may be pushed to the relevant source trees for release in the next version. You can access the patch from: http://lists.mysql.com/commits/129844 3565 Olav Sandstaa 2011-01-28 Fix for Bug#59186 Wrong results of join when ICP is enabled When index condition pushdown was used an extra row appeared in the result set due to parts of the select condition for the second table in the JOIN was not evaluated. This was caused when computing the "remainder" for the second table's select condition after pushing down parts of it to the storage engine. If parts of the select condition was common for both tables in the JOIN and the common part was pushed down for the first table the common part could have the marker field set to ICP_COND_USES_INDEX_ONLY during evaluation of make_cond_for_index() for the first table. If the common part of the select condition was not pushed down for the second table it would still be marked with ICP_COND_USES_INDEX_ONLY when computing the remainder for the select condition for the second table. This would cause that this part of the select condition neither was pushed down to the storage engine nor included in the select condition to be evaluated by the server. The fix for this is to extend make_cond_for_index() so that it clears the marker field for the parts of the item tree that it decides should not be pushed down to the storage engine. This will prevent that common items in select conditions for different tables that have been marked with ICP_COND_USES_INDEX_ONLY when evaluating one table does not keep this value when computing the "remainder" for a following table in a JOIN. The patch also contains some re-write and clean-up code to make_cond_for_index(): -remove unnessary cast for return of NULL. -ensure that make_cond_for_index() is always called with a cond value. @ mysql-test/include/icp_tests.inc Test case for Bug#59186 Wrong results of join when ICP is enabled. @ mysql-test/r/innodb_icp.result Result file for the test for Bug#59186 Wrong results of join when ICP is enabled. @ mysql-test/r/innodb_icp_none.result Result file for the test for Bug#59186 Wrong results of join when ICP is enabled. @ mysql-test/r/myisam_icp.result Result file for the test for Bug#59186 Wrong results of join when ICP is enabled. @ mysql-test/r/myisam_icp_none.result Result file for the test for Bug#59186 Wrong results of join when ICP is enabled. @ sql/sql_select.cc Reset the condition's marker field in make_cond_for_index() when it has determined that this part of the select condition should not be included in the condition to be pushed down to the storage engine. The reason this must be cleared is that if the condition is a common part of for the select condition of two tables in a JOIN operation then the marker field might have gotten the value set to ICP_COND_USES_INDEX_ONLY when evaluating the select condition for the first table. If this is the case we need to reset it to avoid that this part of the select condition is wrongly concluded to not be needed in the select condition to be evaluated by the server after pushing parts of the condition down to the storage engine.
[28 Jan 2011 12:58]
Bugs System
Pushed into mysql-trunk 5.6.2 (revid:olav.sandstaa@oracle.com-20110128125704-qoynw29whkg93549) (version source revid:olav.sandstaa@oracle.com-20110128125704-qoynw29whkg93549) (merge vers: 5.6.2) (pib:24)
[3 Mar 2011 1:32]
Paul DuBois
Noted in 5.6.2 changelog. With index condition pushdown enabled, a join could produce an extra row due to parts of the select condition for the second table in the join not being evaluated. CHANGESET - http://lists.mysql.com/commits/129844