Bug #42991 | invalid memory access and/or crash when using index condition pushdown + innodb | ||
---|---|---|---|
Submitted: | 18 Feb 2009 19:59 | Modified: | 14 May 2011 1:43 |
Reporter: | Shane Bester (Platinum Quality Contributor) | Email Updates: | |
Status: | Closed | Impact on me: | |
Category: | MySQL Server: Optimizer | Severity: | S1 (Critical) |
Version: | 6.0.9,6.0.12,6.0-bzr | OS: | Any |
Assigned to: | Olav Sandstå | CPU Architecture: | Any |
Tags: | index_condition_pushdown, optimizer_switch |
[18 Feb 2009 19:59]
Shane Bester
[18 Feb 2009 20:04]
MySQL Verification Team
import a few times against mysqld under valgrind, wait for errors...
Attachment: bug42991.sql (text/x-sql), 63.73 KiB.
[18 Feb 2009 20:05]
MySQL Verification Team
full debug infos, valgrind outputs, etc.
Attachment: bug42991_debug_valgrind_infos.txt (text/plain), 10.37 KiB.
[19 Feb 2009 7:12]
Sveta Smirnova
Thank you for the report. Valgrind warnings verified as described: ==14704== Invalid read of size 1 ==14704== at 0x8834A1C: my_strnncollsp_simple (ctype-simple.c:167) ==14704== by 0x82944C8: Field_blob::cmp(unsigned char const*, unsigned, unsigned char const*, unsigned) (field.cc:7734) ==14704== by 0x8294999: Field_blob::key_cmp(unsigned char const*, unsigned) (field.cc:7847) ==14704== by 0x82A1A72: key_cmp(st_key_part_info*, unsigned char const*, unsigned) (key.cc:477) ==14704== by 0x840B112: handler::compare_key2(st_key_range*) (handler.cc:5127) ==14704== by 0x868C958: index_cond_func_innodb (ha_innodb.cc:8543) ==14704== by 0x86F3EFE: row_search_for_mysql (row0sel.c:4143) ==14704== by 0x8686A5F: ha_innobase::general_fetch(unsigned char*, unsigned, unsigned) (ha_innodb.cc:4656) ==14704== by 0x8686B55: ha_innobase::index_next(unsigned char*) (ha_innodb.cc:4692) ==14704== by 0x840AE79: handler::read_range_next() (handler.cc:5083) ==14704== by 0x868CA32: ha_innobase::read_range_next() (ha_innodb.cc:8578) ==14704== by 0x8409558: handler::multi_range_read_next(char**) (handler.cc:4304) ==14704== by 0x840A084: DsMrr_impl::dsmrr_next(char**) (handler.cc:4599) ==14704== by 0x868C853: ha_innobase::multi_range_read_next(char**) (ha_innodb.cc:8505) ==14704== by 0x83F12F0: QUICK_RANGE_SELECT::get_next() (opt_range.cc:8558) ==14704== by 0x83FD590: rr_quick(READ_RECORD*) (records.cc:322) ==14704== Address 0x28438cd0 is 96 bytes inside a block of size 16,464 free'd ==14704== at 0x400542D: free (vg_replace_malloc.c:323) ==14704== by 0x8710057: ut_free (ut0mem.c:252) ==14704== by 0x86CFC9D: mem_area_free (mem0pool.c:472) ==14704== by 0x86CF069: mem_heap_block_free (mem0mem.c:524) ==14704== by 0x86CE29C: mem_heap_free_func (mem0mem.ic:487) ==14704== by 0x86F1C12: row_sel_store_mysql_rec (row0sel.c:2606) ==14704== by 0x86F3EE7: row_search_for_mysql (row0sel.c:4141) ==14704== by 0x8686A5F: ha_innobase::general_fetch(unsigned char*, unsigned, unsigned) (ha_innodb.cc:4656) ==14704== by 0x8686B55: ha_innobase::index_next(unsigned char*) (ha_innodb.cc:4692) ==14704== by 0x840AE79: handler::read_range_next() (handler.cc:5083) ==14704== by 0x868CA32: ha_innobase::read_range_next() (ha_innodb.cc:8578) ==14704== by 0x8409558: handler::multi_range_read_next(char**) (handler.cc:4304) ==14704== by 0x840A084: DsMrr_impl::dsmrr_next(char**) (handler.cc:4599) ==14704== by 0x868C853: ha_innobase::multi_range_read_next(char**) (ha_innodb.cc:8505) ==14704== by 0x83F12F0: QUICK_RANGE_SELECT::get_next() (opt_range.cc:8558) ==14704== by 0x83FD590: rr_quick(READ_RECORD*) (records.cc:322)
[19 Jun 2009 10:13]
MySQL Verification Team
Got another crash with engine_condition_pushdown=1: Thread 11: Invalid write of size 4 at: mem_pool_fill_free_list (mem0pool.c:302) by: mem_pool_fill_free_list (mem0pool.c:286) by: mem_area_alloc (mem0pool.c:346) by: mem_heap_create_block (mem0mem.c:370) by: mem_heap_add_block (mem0mem.c:465) by: lock_rec_create (mem0mem.ic:155) by: lock_rec_lock (lock0lock.c:1972) by: lock_clust_rec_read_check_and_lock (lock0lock.c:5159) by: row_search_for_mysql (row0sel.c:851) by: ha_innobase::general_fetch (ha_innodb.cc:4671) by: ha_innobase::rnd_next(unsigned char*) (ha_innodb.cc:4860) by: rr_sequential(READ_RECORD*) (records.cc:390) Address 0x10 is not stack'd, malloc'd or (recently) free'd
[8 Oct 2009 12:24]
Guilhem Bichot
goes away / comes back when disabling/enabling ICP in InnoDB
[30 Nov 2009 20:38]
Sergey Petrunya
If I add EXPLAIN to the testcase, I get this: +explain select * from `table5` where (col2 <= '6566-06-15' AND col24 <> 'd') group by `col83` order by `col83` desc ; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE table5 range PRIMARY,idx1,idx3,idx4 idx3 89 NULL 1 Using index condition; Using where; Using temporary; Using filesort it is using idx3 which is defined as KEY `idx3` (`col2`(86)) push_index_cond() and co correctly detect that "col2 <= '6566-06-15" part of the condition cannot be checked by using the index. The "col24 <> 'd'" part does get pushed (col24 is a clustered primary key, so it is part of the index). Since index condition is present, it will try to decode the entire index tuple. I didn't investigate what exactly goes wrong when that is done, but it can be wrong on various levels as we don't ever try decoding blob columns from index images of their prefixes. Suggested solution right now is to disable index condition for indexes that have partially-covered columns (check key_part->flag & HA_PART_KEY_SEG for all key parts?)
[4 Feb 2010 11:39]
Olav Sandstå
I have simplified the test case for reproducing this valgrind error: CREATE TABLE t1 ( c1 TINYTEXT, c2 INT NOT NULL, PRIMARY KEY (c2), KEY id1 (c1(4)) ) ENGINE=InnoDB; INSERT INTO t1 VALUES ('Anastasia', 5); INSERT INTO t1 VALUES ('Karianne', 4); SELECT * FROM t1 WHERE (c1 <= '6566-06-15' AND c2 <> 3);
[4 Feb 2010 11:41]
Olav Sandstå
Simplied version of test case for reproducing bug
Attachment: bug42991.test (application/octet-stream, text), 671 bytes.
[11 Feb 2010 20:33]
Olav Sandstå
Here is a summary of what causes valgrind to report reading of uninitialized data (using the simplified test case as an example). 1. It uses the id1 index (defined as KEY c1(4) ) and the following Index condition is push down to InnoDB: `test`.`t1`.`c2` <> 3 This is similar to what Sergey Petrunya have commented above about the original test case. 2. In build_template() in ha_innodb.cc the mysql_template array is set up to include which fields to read from the index record (since ICP is in use). Both fields of the index entry (c1(4), c2) is considered for inclusion but only c2 is added to the mysql_template array's "index part". The reason c1 is not included is due to the following test: register const ibool index_covers_field = field->part_of_key.is_set(file->active_index); This is evaluated to false since the c1 field is not part of the key/index and because of this c1 is not included in mysql_template array (at least not the index part of it - it is included later). 3. When the first row is read from the table and the code at the idx_cond_check label in row_search_for_mysql() (in row/row0sel.c) reads the content of the index entry based on the mysql_template array: row_sel_store_mysql_rec(buf, prebuilt, rec, offsets, 0, prebuilt->n_index_fields); This only reads the c2 key part from the index entry (not the c1(4) key part) into the mysql record. 4. Then the actual ICP test is run: res= prebuilt->idx_cond_func(prebuilt->idx_cond_func_arg); This calls the following function (see ha_innodb.cc): /* Index condition check function to be called from within Innobase. See note on ICP_RESULT for return values description. */ static uint index_cond_func_innodb(void *arg) { ha_innobase *h= (ha_innobase*)arg; if (h->end_range) { if (h->compare_key2(h->end_range) > 0) return 2; /* caller should return HA_ERR_END_OF_FILE already */ } return test(h->pushed_idx_cond->val_int()); } In this case a range read is used and before the actual index condition is evaluated a test whether the index-entry is within the range is done (h->compare_key2(h->end_range). The end of the range is defined as: c1 <= '6566-06-15' This is compared against the content of the "c1" field in the mysql_record which at this point has not yet been read into the mysql_record (due to this field not being included in the "index part" of the mysql_template array). This leads to reading "un-initialized data". In this case the test case did produce the correct result but based on the above it would likely be possible to create a test case that produced the wrong result. A patch that disables ICP in the case where the index entry only contains a prefix of the complete field value will be submitted shortly (test similar to what suggested by Sergey).
[11 Feb 2010 21:34]
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/100041 3882 Olav.Sandstaa@sun.com 2010-02-11 Fix for Bug#42991 invalid memory access and/or crash when using index condition pushdown + innodb The valgrind error about reading uninitialized memory occurs because the index used for index condition pushdown contained a prefix of a BLOB field. This lead to this field not being read in from neither the index nor the real record due to not "covering the real field" but was still used when the index condition pushdown expression and the end of range test was executed. The fix for this problem is to not accept a pushed index condition in the case where the index contains key parts that contains only a part of the real field. The test case for this bug will be integrated into the innodb_icp test when the patch for Bug#43360 has been pushed. @ mysql-test/r/bug42991.result Result file for test for Bug#42991. @ mysql-test/t/bug42991.test Test case for Bug#42991. @ storage/innobase/handler/ha_innodb.cc When considering if InnoDB should accept a pushed index condition add an extra test to check that the index does not contains key parts that are not completely stored in the index entry (e.g. prefix of BLOB fields). If this is the case then reject the pushed index condition.
[5 Mar 2010 17: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/102454
[5 May 2010 11:35]
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/107499
[1 Sep 2010 9:57]
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/117319 3232 Olav Sandstaa 2010-09-01 Fix for Bug#42991 invalid memory access and/or crash when using index condition pushdown + innodb The valgrind error about reading uninitialized memory occurs because the index used for the range scan and index condition pushdown contained a prefix of a BLOB (TINYTEXT) field. This lead to this field not being read in from the index due to not "covering the real field" but was still used when the the end of range test was executed. Using the attached test case as example: The optimizer decides to use an index on a prefix of a TINYTEXT column for doing a range scan. In this case InnoDB will evaluate records against the end range value. In the case where ICP is not in use it will do the end-range check against the TINYTEXT value read from the base record. But in this case ICP is in use and a condtion on the primary key is pushed down to InnoDB. When ICP is in use the end-range evaluation is done by the "icp code" in InnoDB and evaluated against values read from the index (see the code in the function index_cond_func_innodb() in ha_innodb.cc). Unfortunately, since the index entry only contains a prefix of the TINYTEXT field it is not read from the index and the end-range evaluation in index_cond_func_innodb() is done against "uninitialized data". The implemented solution for this problem is to extend the ICP implementation in InnoDB to also include support for reading index entries that are prefixes of BLOB columns and use these for doing end range evaluation. When setting up the mysql_template array (in build_template() in ha_innodb.cc) that contains which fields to read from index entries and base records we will now include reading index fields that are prefixes of columns in the base record. Since these only contains the prefix for the value it is necessary to read both the index entry and the corresponding column value from the base record in this case. @ mysql-test/include/icp_tests.inc Test case for Bug#42991 "invalid memory access and/or crash when using index condition pushdown + innodb". @ mysql-test/r/innodb_icp.result Result file for test case for Bug#42991 "invalid memory access and/or crash when using index condition pushdown + innodb". @ mysql-test/r/innodb_icp_all.result Result file for test case for Bug#42991 "invalid memory access and/or crash when using index condition pushdown + innodb". @ mysql-test/r/innodb_icp_none.result Result file for test case for Bug#42991 "invalid memory access and/or crash when using index condition pushdown + innodb". @ mysql-test/r/myisam_icp.result Result file for test case for Bug#42991 "invalid memory access and/or crash when using index condition pushdown + innodb". @ mysql-test/r/myisam_icp_all.result Result file for test case for Bug#42991 "invalid memory access and/or crash when using index condition pushdown + innodb". @ mysql-test/r/myisam_icp_none.result Result file for test case for Bug#42991 "invalid memory access and/or crash when using index condition pushdown + innodb". @ storage/innobase/dict/dict0dict.c Add new function for reading out the position of a specified column or prefix of a column in an index entry: dict_index_get_nth_col_or_prefix_pos(). The implementation is done by moving the code for the existing dict_index_get_nth_col_pos() function to a common function and having the new function (dict_index_get_nth_col_or_prefix_pos() and the existing function (dict_index_get_nth_col_pos())calling this common function with a boolean parameter saying if it should include prefixes in the search for a column in the index entry. @ storage/innobase/handler/ha_innodb.cc Extend build_template() to also including adding reading of prefixes of column values from index entries when ICP is in use. This is done by including these in the mysql_template array. Since we now might read both a prefix of a column value from the index entry and the actual value from the base record the size of the mysql_template array has to be doubled when ICP is in use. @ storage/innobase/include/dict0dict.h Add new function for reading out the position of a specified column or prefix of a column in an index entry: dict_index_get_nth_col_or_prefix_pos(). @ storage/innobase/include/row0mysql.h Add a member to the row_prebuilt_struct to store the size of the allocated mysql_template array.
[8 Nov 2010 8:21]
Olav Sandstå
The fix for this problem has been included in the following change set: marko.makela@oracle.com-20101025120429-m275rpb5ylbqdccs .
[8 Nov 2010 8:49]
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/123044
[8 Nov 2010 8:53]
Olav Sandstå
Test case pushed to mysql-next-mr-opt-backporting with revision id: olav.sandstaa@oracle.com-20101108084916-favb0td7g7qrajkh .
[5 Dec 2010 12:39]
Bugs System
Pushed into mysql-trunk 5.6.1 (revid:alexander.nozdrin@oracle.com-20101205122447-6x94l4fmslpbttxj) (version source revid:alexander.nozdrin@oracle.com-20101205122447-6x94l4fmslpbttxj) (merge vers: 5.6.1) (pib:23)
[11 Dec 2010 17:41]
Paul DuBois
Bug does not appear in any released 5.6.x version. No changelog entry needed.