Bug #85126 | Delete by range in presence of partitioning and no PK always picks wrong index | ||
---|---|---|---|
Submitted: | 22 Feb 2017 10:42 | Modified: | 28 Feb 2017 7:15 |
Reporter: | Riccardo Pizzi | Email Updates: | |
Status: | Verified | Impact on me: | |
Category: | MySQL Server: Optimizer | Severity: | S1 (Critical) |
Version: | 5.6.35 | OS: | Any |
Assigned to: | CPU Architecture: | Any | |
Tags: | partitioning |
[22 Feb 2017 10:42]
Riccardo Pizzi
[22 Feb 2017 11:09]
Riccardo Pizzi
This will only happen when issuing a DML (DELETE or UPDATE), while SELECT will always use the right index
[22 Feb 2017 13:10]
Riccardo Pizzi
Further testing seems to indicate that the problem happens when the first partition of the table, which contains zero datetime values ('0000-00-00 00:00:00'), becomes the bigger partition in town. This seem to trigger the bug (analyze table may be required after load to start it). Probably due to fact that statistics are only computed on largest partition. See also https://bugs.mysql.com/bug.php?id=44059. Actually, it seems to happen if/when the largest partition is outside the pruning range, no matter the date value (tried with 2000-01-01 and wrong index is still used).
[22 Feb 2017 14:22]
Riccardo Pizzi
Further demonstration from one of our production machines. Here, there is a partition that contains rows with EXPIRE_DATE < NOW(), this is the largest partition in the table, it is called p358. If we run explain without including this partition (first example), the correct index is used. If we include this partition (second example), it uses the wrong one. >explain partitions DELETE FROM BIG_STORAGE_00 partition (p359,p360,p361,p362,p363,p364,p365,p366,p367,p368,p369,p370,p371,p372,p373,p374,p375,p376,p377,p378,p379,p380,p381,p382,p383,p384,p385,p386,p387,p388,p389,p390) WHERE HASH_ID = SHA2('6fpsrcwcccjigdek2851400rxuafibyepqny', 256) AND EXPIRE_DATE > NOW() ; +----+-------------+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+------------------------+---------+---------+-------+------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+------------------------+---------+---------+-------+------+-------------+ | 1 | SIMPLE | BIG_STORAGE_00 | p359,p360,p361,p362,p363,p364,p365,p366,p367,p368,p369,p370,p371,p372,p373,p374,p375,p376,p377,p378,p379,p380,p381,p382,p383,p384,p385,p386,p387,p388,p389,p390 | range | EXPIRE_DATE_IX,HASH_ID | HASH_ID | 64 | const | 1 | Using where | +----+-------------+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+------------------------+---------+---------+-------+------+-------------+ 1 row in set (0.00 sec) >explain partitions DELETE FROM BIG_STORAGE_00 partition (p358,p359,p360,p361,p362,p363,p364,p365,p366,p367,p368,p369,p370,p371,p372,p373,p374,p375,p376,p377,p378,p379,p380,p381,p382,p383,p384,p385,p386,p387,p388,p389,p390) WHERE HASH_ID = SHA2('6fpsrcwcccjigdek2851400rxuafibyepqny', 256) AND EXPIRE_DATE > NOW() ; +----+-------------+----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+------------------------+----------------+---------+-------+------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+------------------------+----------------+---------+-------+------+-------------+ | 1 | SIMPLE | BIG_STORAGE_00 | p358,p359,p360,p361,p362,p363,p364,p365,p366,p367,p368,p369,p370,p371,p372,p373,p374,p375,p376,p377,p378,p379,p380,p381,p382,p383,p384,p385,p386,p387,p388,p389,p390 | range | EXPIRE_DATE_IX,HASH_ID | EXPIRE_DATE_IX | 5 | const | 1 | Using where | +----+-------------+----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+------------------------+----------------+---------+-------+------+-------------+ 1 row in set (0.00 sec)
[22 Feb 2017 17:26]
Riccardo Pizzi
Looks like the optimizer has given the exact same cost to both indexes: 5.81 (based on the largest partition which happens to have rows < EXPIRE_DATE). Since they have the exact same cost, the optimizer decides to take the first out of the two. Guess what, rebuilding the table with order of indexes inverted fixes the problem!! I wonder why can't the optimizer know that EXPIRE_DATE is a partitioning key and all costs being equal, decide to use the other index, and reserve EXPIRE_DATE for pruning as intended? EXPIRE_DATE_IX first, HASH_ID_IX last: opt_range.cc: 264: get_key_scans_params: opt: range_scan_alternatives: starting struct opt_range.cc: 264: get_key_scans_params: opt: (null): starting struct opt_range.cc: 299: get_key_scans_params: opt: index: "EXPIRE_DATE_IX" opt_range.cc: 9541: check_quick_select: exit: Records: 4 opt_range.cc: 264: get_key_scans_params: opt: ranges: starting struct opt_range.cc: 299: get_key_scans_params: opt: (null): "2017-02-22 17:28:51 < EXPIRE_DATE" opt_range.cc: 279: get_key_scans_params: opt: ranges: ending struct opt_range.cc: 313: get_key_scans_params: opt: index_dives_for_eq_ranges: 1 opt_range.cc: 313: get_key_scans_params: opt: rowid_ordered: 0 opt_range.cc: 313: get_key_scans_params: opt: using_mrr: 0 opt_range.cc: 313: get_key_scans_params: opt: index_only: 0 opt_range.cc: 336: get_key_scans_params: opt: rows: 4 opt_range.cc: 347: get_key_scans_params: opt: cost: 5.81 opt_range.cc: 313: get_key_scans_params: opt: chosen: 1 opt_range.cc: 279: get_key_scans_params: opt: (null): ending struct opt_range.cc: 264: get_key_scans_params: opt: (null): starting struct opt_range.cc: 299: get_key_scans_params: opt: index: "HASH_ID" opt_range.cc: 9541: check_quick_select: exit: Records: 4 opt_range.cc: 264: get_key_scans_params: opt: ranges: starting struct opt_range.cc: 299: get_key_scans_params: opt: (null): "925445622fe6c9da0a432b3e686b807557c215f04233b59a94b7eff1cb6ef3d9 <= HASH_ID <= 925445622fe6c9da0a432b3e686b807557c215f04233b59a94b7eff1cb6ef3d9" opt_range.cc: 279: get_key_scans_params: opt: ranges: ending struct opt_range.cc: 313: get_key_scans_params: opt: index_dives_for_eq_ranges: 1 opt_range.cc: 313: get_key_scans_params: opt: rowid_ordered: 1 opt_range.cc: 313: get_key_scans_params: opt: using_mrr: 0 opt_range.cc: 313: get_key_scans_params: opt: index_only: 0 opt_range.cc: 336: get_key_scans_params: opt: rows: 4 opt_range.cc: 347: get_key_scans_params: opt: cost: 5.81 opt_range.cc: 313: get_key_scans_params: opt: chosen: 0 opt_range.cc: 299: get_key_scans_params: opt: cause: "cost" opt_range.cc: 279: get_key_scans_params: opt: (null): ending struct opt_range.cc: 13830: print_sel_tree: info: SEL_TREE: 0x7f58c00b1fd8 (ROR scans) scans: HASH_ID opt_range.cc: 5676: get_key_scans_params: info: Returning range plan for key EXPIRE_DATE_IX, cost 5.81, records 4 HASH_ID_IX first, EXPIRE_DATE_IX last: opt_range.cc: 264: get_key_scans_params: opt: range_scan_alternatives: starting struct opt_range.cc: 264: get_key_scans_params: opt: (null): starting struct opt_range.cc: 299: get_key_scans_params: opt: index: "HASH_ID_IX" opt_range.cc: 9541: check_quick_select: exit: Records: 4 opt_range.cc: 264: get_key_scans_params: opt: ranges: starting struct opt_range.cc: 299: get_key_scans_params: opt: (null): "925445622fe6c9da0a432b3e686b807557c215f04233b59a94b7eff1cb6ef3d9 <= HASH_ID <= 925445622fe6c9da0a432b3e686b807557c215f04233b59a94b7eff1cb6ef3d9" opt_range.cc: 279: get_key_scans_params: opt: ranges: ending struct opt_range.cc: 313: get_key_scans_params: opt: index_dives_for_eq_ranges: 1 opt_range.cc: 313: get_key_scans_params: opt: rowid_ordered: 1 opt_range.cc: 313: get_key_scans_params: opt: using_mrr: 0 opt_range.cc: 313: get_key_scans_params: opt: index_only: 0 opt_range.cc: 336: get_key_scans_params: opt: rows: 4 opt_range.cc: 347: get_key_scans_params: opt: cost: 5.81 opt_range.cc: 313: get_key_scans_params: opt: chosen: 1 opt_range.cc: 279: get_key_scans_params: opt: (null): ending struct opt_range.cc: 264: get_key_scans_params: opt: (null): starting struct opt_range.cc: 299: get_key_scans_params: opt: index: "EXPIRE_DATE_IX" opt_range.cc: 9541: check_quick_select: exit: Records: 4 opt_range.cc: 264: get_key_scans_params: opt: ranges: starting struct opt_range.cc: 299: get_key_scans_params: opt: (null): "2017-02-22 18:20:05 < EXPIRE_DATE" opt_range.cc: 279: get_key_scans_params: opt: ranges: ending struct opt_range.cc: 313: get_key_scans_params: opt: index_dives_for_eq_ranges: 1 opt_range.cc: 313: get_key_scans_params: opt: rowid_ordered: 0 opt_range.cc: 313: get_key_scans_params: opt: using_mrr: 0 opt_range.cc: 313: get_key_scans_params: opt: index_only: 0 opt_range.cc: 336: get_key_scans_params: opt: rows: 4 opt_range.cc: 347: get_key_scans_params: opt: cost: 5.81 opt_range.cc: 313: get_key_scans_params: opt: chosen: 0 opt_range.cc: 299: get_key_scans_params: opt: cause: "cost" opt_range.cc: 279: get_key_scans_params: opt: (null): ending struct opt_range.cc: 13830: print_sel_tree: info: SEL_TREE: 0x7f7da4095838 (ROR scans) scans: HASH_ID_IX opt_range.cc: 5676: get_key_scans_params: info: Returning range plan for key HASH_ID_IX, cost 5.81, records 4
[28 Feb 2017 7:15]
MySQL Verification Team
Hello Riccardo, Thank you for the report and test case. Thanks, Umesh