From febda72f628cbd3c9ca638f0022ef1c765d41c2c Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 21 Nov 2004 11:51:19 +0300 Subject: [PATCH] Fix for BUG#4177: * Make index merge quick selects code allow perform several scans. * Delay additional handler objects creation till row retrieval is started. include/queues.h: Added trivial queue_remove_all macro mysql-test/r/index_merge.result: Testcase for BUG#4177 mysql-test/t/index_merge.test: Testcase for BUG#4177 sql/opt_range.cc: Fix for BUG#4177: * For any quick select, row retrieval can be performed several times. Now all index_merge quick selects code handles such cases properly. * In QUICK_INDEX_MERGE_SELECT we use one handler object for all merged scans, and it was possible that in destructor several cleanup functions were called * Additionally - Removed redundant QUICK_INDEX_MERGE_SELECT members. - Now QUICK_ROR_*_SELECTs create additional handler objects only when row retrieval is started So if join optimizer chooses other access method, we don't create/delete handlers. --- include/queues.h | 1 + mysql-test/r/index_merge.result | 53 ++++++++++++++- mysql-test/t/index_merge.test | 49 +++++++++++++- sql/opt_range.cc | 114 ++++++++++++++++++++++---------- sql/opt_range.h | 18 ++--- 5 files changed, 188 insertions(+), 47 deletions(-) diff --git a/include/queues.h b/include/queues.h index ac15b09719b..02ab768198e 100644 --- a/include/queues.h +++ b/include/queues.h @@ -53,6 +53,7 @@ int resize_queue(QUEUE *queue, uint max_elements); void delete_queue(QUEUE *queue); void queue_insert(QUEUE *queue,byte *element); byte *queue_remove(QUEUE *queue,uint idx); +#define queue_remove_all(queue) { (queue)->elements= 0; } void _downheap(QUEUE *queue,uint idx); void queue_fix(QUEUE *queue); #define is_queue_inited(queue) ((queue)->root != 0) diff --git a/mysql-test/r/index_merge.result b/mysql-test/r/index_merge.result index 6b41992a733..4ccec5ef0d8 100644 --- a/mysql-test/r/index_merge.result +++ b/mysql-test/r/index_merge.result @@ -1,4 +1,4 @@ -drop table if exists t0, t1, t2, t3,t4; +drop table if exists t0, t1, t2, t3, t4; create table t0 ( key1 int not null, @@ -335,4 +335,55 @@ key1 key2 key3 key4 key5 key6 key7 key8 select count(*) from t0; count(*) 1021 +drop table t4; +create table t4 (a int); +insert into t4 values (1),(4),(3); +set @save_join_buffer_size=@@join_buffer_size; +set join_buffer_size= 4000; +show variables like 'join_buffer_size'; +Variable_name Value +join_buffer_size 8228 +explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) +from t0 as A force index(i1,i2), t0 as B force index (i1,i2) +where (A.key1 < 500000 or A.key2 < 3) +and (B.key1 < 500000 or B.key2 < 3); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE A index_merge i1,i2 i1,i2 4,4 NULL 1016 Using sort_union(i1,i2); Using where +1 SIMPLE B index_merge i1,i2 i1,i2 4,4 NULL 1016 Using sort_union(i1,i2); Using where +select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) +from t0 as A force index(i1,i2), t0 as B force index (i1,i2) +where (A.key1 < 500000 or A.key2 < 3) +and (B.key1 < 500000 or B.key2 < 3); +max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) +10240 +update t0 set key1=1; +explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) +from t0 as A force index(i1,i2), t0 as B force index (i1,i2) +where (A.key1 = 1 or A.key2 = 1) +and (B.key1 = 1 or B.key2 = 1); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE A index_merge i1,i2 i1,i2 4,4 NULL 1020 Using union(i1,i2); Using where +1 SIMPLE B index_merge i1,i2 i1,i2 4,4 NULL 1020 Using union(i1,i2); Using where +select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) +from t0 as A force index(i1,i2), t0 as B force index (i1,i2) +where (A.key1 = 1 or A.key2 = 1) +and (B.key1 = 1 or B.key2 = 1); +max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) +8194 +alter table t0 add filler1 char(200), add filler2 char(200), add filler3 char(200); +update t0 set key2=1, key3=1, key4=1, key5=1,key6=1,key7=1 where key7 < 500; +explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) +from t0 as A, t0 as B +where (A.key1 = 1 and A.key2 = 1 and A.key3 = 1 and A.key4=1 and A.key5=1 and A.key6=1 and A.key7 = 1 or A.key8=1) +and (B.key1 = 1 and B.key2 = 1 and B.key3 = 1 and B.key4=1 and B.key5=1 and B.key6=1 and B.key7 = 1 or B.key8=1); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE A index_merge i1,i2,i3,i4,i5,i6,i7,i8 i2,i3,i4,i5,i6,i8 4,4,4,4,4,4 NULL 16 Using union(intersect(i2,i3,i4,i5,i6),i8); Using where +1 SIMPLE B index_merge i1,i2,i3,i4,i5,i6,i7,i8 i2,i3,i4,i5,i6,i8 4,4,4,4,4,4 NULL 16 Using union(intersect(i2,i3,i4,i5,i6),i8); Using where +select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) +from t0 as A, t0 as B +where (A.key1 = 1 and A.key2 = 1 and A.key3 = 1 and A.key4=1 and A.key5=1 and A.key6=1 and A.key7 = 1 or A.key8=1) +and (B.key1 = 1 and B.key2 = 1 and B.key3 = 1 and B.key4=1 and B.key5=1 and B.key6=1 and B.key7 = 1 or B.key8=1); +max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) +8186 +set join_buffer_size= @save_join_buffer_size; drop table t0, t1, t2, t3, t4; diff --git a/mysql-test/t/index_merge.test b/mysql-test/t/index_merge.test index a4f7b71e5a3..3ab68d85125 100644 --- a/mysql-test/t/index_merge.test +++ b/mysql-test/t/index_merge.test @@ -1,9 +1,8 @@ # # Index merge tests # - --disable_warnings -drop table if exists t0, t1, t2, t3,t4; +drop table if exists t0, t1, t2, t3, t4; --enable_warnings # Create and fill a table with simple keys @@ -278,4 +277,48 @@ delete from t0 where key1 < 3 or key2 < 4; select * from t0 where key1 < 3 or key2 < 4; select count(*) from t0; -drop table t0, t1, t2, t3, t4; +# Test for BUG#4177 +drop table t4; +create table t4 (a int); +insert into t4 values (1),(4),(3); +set @save_join_buffer_size=@@join_buffer_size; +set join_buffer_size= 4000; +show variables like 'join_buffer_size'; +explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) + from t0 as A force index(i1,i2), t0 as B force index (i1,i2) + where (A.key1 < 500000 or A.key2 < 3) + and (B.key1 < 500000 or B.key2 < 3); + +select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) + from t0 as A force index(i1,i2), t0 as B force index (i1,i2) + where (A.key1 < 500000 or A.key2 < 3) + and (B.key1 < 500000 or B.key2 < 3); + +update t0 set key1=1; +explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) + from t0 as A force index(i1,i2), t0 as B force index (i1,i2) + where (A.key1 = 1 or A.key2 = 1) + and (B.key1 = 1 or B.key2 = 1); + +select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) + from t0 as A force index(i1,i2), t0 as B force index (i1,i2) + where (A.key1 = 1 or A.key2 = 1) + and (B.key1 = 1 or B.key2 = 1); + +alter table t0 add filler1 char(200), add filler2 char(200), add filler3 char(200); +update t0 set key2=1, key3=1, key4=1, key5=1,key6=1,key7=1 where key7 < 500; +explain select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) + from t0 as A, t0 as B + where (A.key1 = 1 and A.key2 = 1 and A.key3 = 1 and A.key4=1 and A.key5=1 and A.key6=1 and A.key7 = 1 or A.key8=1) + and (B.key1 = 1 and B.key2 = 1 and B.key3 = 1 and B.key4=1 and B.key5=1 and B.key6=1 and B.key7 = 1 or B.key8=1); + +select max(A.key1 + B.key1 + A.key2 + B.key2 + A.key3 + B.key3 + A.key4 + B.key4 + A.key5 + B.key5) + from t0 as A, t0 as B + where (A.key1 = 1 and A.key2 = 1 and A.key3 = 1 and A.key4=1 and A.key5=1 and A.key6=1 and A.key7 = 1 or A.key8=1) + and (B.key1 = 1 and B.key2 = 1 and B.key3 = 1 and B.key4=1 and B.key5=1 and B.key6=1 and B.key7 = 1 or B.key8=1); + +set join_buffer_size= @save_join_buffer_size; +# Test for BUG#4177 ends + +drop table t0, t1, t2, t3, t4; + diff --git a/sql/opt_range.cc b/sql/opt_range.cc index f036cbc799b..ec7434d215b 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -777,8 +777,7 @@ QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT() QUICK_INDEX_MERGE_SELECT::QUICK_INDEX_MERGE_SELECT(THD *thd_param, TABLE *table) - :cur_quick_it(quick_selects),pk_quick_select(NULL),unique(NULL), - thd(thd_param) + :pk_quick_select(NULL), thd(thd_param) { DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::QUICK_INDEX_MERGE_SELECT"); index= MAX_KEY; @@ -790,17 +789,14 @@ QUICK_INDEX_MERGE_SELECT::QUICK_INDEX_MERGE_SELECT(THD *thd_param, int QUICK_INDEX_MERGE_SELECT::init() { - cur_quick_it.rewind(); - cur_quick_select= cur_quick_it++; - return 0; + DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::init"); + DBUG_RETURN(0); } int QUICK_INDEX_MERGE_SELECT::reset() { - int result; DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::reset"); - result= cur_quick_select->reset() || prepare_unique(); - DBUG_RETURN(result); + DBUG_RETURN(read_keys_and_merge()); } bool @@ -820,8 +816,12 @@ QUICK_INDEX_MERGE_SELECT::push_quick_back(QUICK_RANGE_SELECT *quick_sel_range) QUICK_INDEX_MERGE_SELECT::~QUICK_INDEX_MERGE_SELECT() { + List_iterator_fast quick_it(quick_selects); + QUICK_RANGE_SELECT* quick; DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::~QUICK_INDEX_MERGE_SELECT"); - delete unique; + quick_it.rewind(); + while ((quick= quick_it++)) + quick->file= NULL; quick_selects.delete_elements(); delete pk_quick_select; free_root(&alloc,MYF(0)); @@ -833,7 +833,8 @@ QUICK_ROR_INTERSECT_SELECT::QUICK_ROR_INTERSECT_SELECT(THD *thd_param, TABLE *table, bool retrieve_full_rows, MEM_ROOT *parent_alloc) - : cpk_quick(NULL), thd(thd_param), need_to_fetch_row(retrieve_full_rows) + : cpk_quick(NULL), thd(thd_param), need_to_fetch_row(retrieve_full_rows), + scans_inited(false) { index= MAX_KEY; head= table; @@ -859,8 +860,9 @@ QUICK_ROR_INTERSECT_SELECT::QUICK_ROR_INTERSECT_SELECT(THD *thd_param, int QUICK_ROR_INTERSECT_SELECT::init() { - /* Check if last_rowid was successfully allocated in ctor */ - return !last_rowid; + DBUG_ENTER("QUICK_ROR_INTERSECT_SELECT::init"); + /* Check if last_rowid was successfully allocated in ctor */ + DBUG_RETURN(!last_rowid); } @@ -953,7 +955,7 @@ int QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan(bool reuse_handler) DBUG_ENTER("QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan"); /* Initialize all merged "children" quick selects */ - DBUG_ASSERT(!(need_to_fetch_row && !reuse_handler)); + DBUG_ASSERT(!need_to_fetch_row || reuse_handler); if (!need_to_fetch_row && reuse_handler) { quick= quick_it++; @@ -995,7 +997,14 @@ int QUICK_ROR_INTERSECT_SELECT::init_ror_merged_scan(bool reuse_handler) int QUICK_ROR_INTERSECT_SELECT::reset() { DBUG_ENTER("QUICK_ROR_INTERSECT_SELECT::reset"); - DBUG_RETURN(init_ror_merged_scan(TRUE)); + if (!scans_inited && init_ror_merged_scan(TRUE)) + DBUG_RETURN(1); + scans_inited= true; + List_iterator_fast it(quick_selects); + QUICK_RANGE_SELECT *quick; + while ((quick= it++)) + quick->reset(); + DBUG_RETURN(0); } @@ -1034,7 +1043,7 @@ QUICK_ROR_INTERSECT_SELECT::~QUICK_ROR_INTERSECT_SELECT() QUICK_ROR_UNION_SELECT::QUICK_ROR_UNION_SELECT(THD *thd_param, TABLE *table) - :thd(thd_param) + : thd(thd_param), scans_inited(false) { index= MAX_KEY; head= table; @@ -1057,18 +1066,19 @@ QUICK_ROR_UNION_SELECT::QUICK_ROR_UNION_SELECT(THD *thd_param, int QUICK_ROR_UNION_SELECT::init() { + DBUG_ENTER("QUICK_ROR_UNION_SELECT::init"); if (init_queue(&queue, quick_selects.elements, 0, FALSE , QUICK_ROR_UNION_SELECT::queue_cmp, (void*) this)) { bzero(&queue, sizeof(QUEUE)); - return 1; + DBUG_RETURN(1); } if (!(cur_rowid= (byte*)alloc_root(&alloc, 2*head->file->ref_length))) - return 1; + DBUG_RETURN(1); prev_rowid= cur_rowid + head->file->ref_length; - return 0; + DBUG_RETURN(0); } @@ -1106,6 +1116,18 @@ int QUICK_ROR_UNION_SELECT::reset() int error; DBUG_ENTER("QUICK_ROR_UNION_SELECT::reset"); have_prev_rowid= FALSE; + if (!scans_inited) + { + QUICK_SELECT_I *quick; + List_iterator_fast it(quick_selects); + while ((quick= it++)) + { + if (quick->init_ror_merged_scan(FALSE)) + DBUG_RETURN(1); + } + scans_inited= true; + } + queue_remove_all(&queue); /* Initialize scans for merged quick selects and put all merged quick selects into the queue. @@ -1113,7 +1135,7 @@ int QUICK_ROR_UNION_SELECT::reset() List_iterator_fast it(quick_selects); while ((quick= it++)) { - if (quick->init_ror_merged_scan(FALSE)) + if (quick->reset()) DBUG_RETURN(1); if ((error= quick->get_next())) { @@ -1591,7 +1613,6 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, DBUG_PRINT("enter",("keys_to_use: %lu prev_tables: %lu const_tables: %lu", keys_to_use.to_ulonglong(), (ulong) prev_tables, (ulong) const_tables)); - delete quick; quick=0; needed_reg.clear_all(); @@ -5545,22 +5566,29 @@ err: /* - Fetch all row ids into unique. - + Perform key scans for all used indexes (except CPK), get rowids and merge + them into an ordered non-recurrent sequence of rowids. + + The merge/duplicate removal is performed using Unique class. We put all + rowids into Unique, get the sorted sequence and destroy the Unique. + If table has a clustered primary key that covers all rows (TRUE for bdb and innodb currently) and one of the index_merge scans is a scan on PK, then - primary key scan rowids are not put into Unique and also - rows that will be retrieved by PK scan are not put into Unique + rows that will be retrieved by PK scan are not put into Unique and + primary key scan is not performed here, it is performed later separately. RETURN 0 OK other error */ -int QUICK_INDEX_MERGE_SELECT::prepare_unique() +int QUICK_INDEX_MERGE_SELECT::read_keys_and_merge() { + List_iterator_fast cur_quick_it(quick_selects); + QUICK_RANGE_SELECT* cur_quick; int result; + Unique *unique; DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::prepare_unique"); /* We're going to just read rowids. */ @@ -5575,7 +5603,17 @@ int QUICK_INDEX_MERGE_SELECT::prepare_unique() */ head->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); - cur_quick_select->init(); + cur_quick_it.rewind(); + cur_quick= cur_quick_it++; + DBUG_ASSERT(cur_quick); + + /* + We reuse the same instance of handler so we need to call both init and + reset here. + */ + if (cur_quick->init()) + DBUG_RETURN(1); + cur_quick->reset(); unique= new Unique(refpos_order_cmp, (void *)head->file, head->file->ref_length, @@ -5584,24 +5622,28 @@ int QUICK_INDEX_MERGE_SELECT::prepare_unique() DBUG_RETURN(1); for (;;) { - while ((result= cur_quick_select->get_next()) == HA_ERR_END_OF_FILE) + while ((result= cur_quick->get_next()) == HA_ERR_END_OF_FILE) { - cur_quick_select->range_end(); - cur_quick_select= cur_quick_it++; - if (!cur_quick_select) + cur_quick->range_end(); + cur_quick= cur_quick_it++; + if (!cur_quick) break; - if (cur_quick_select->init()) + if (cur_quick->file->inited != handler::NONE) + cur_quick->file->ha_index_end(); + if (cur_quick->init()) DBUG_RETURN(1); - /* QUICK_RANGE_SELECT::reset never fails */ - cur_quick_select->reset(); + cur_quick->reset(); } if (result) { if (result != HA_ERR_END_OF_FILE) + { + cur_quick->range_end(); DBUG_RETURN(result); + } break; } @@ -5612,8 +5654,8 @@ int QUICK_INDEX_MERGE_SELECT::prepare_unique() if (pk_quick_select && pk_quick_select->row_in_ranges()) continue; - cur_quick_select->file->position(cur_quick_select->record); - result= unique->unique_add((char*)cur_quick_select->file->ref); + cur_quick->file->position(cur_quick->record); + result= unique->unique_add((char*)cur_quick->file->ref); if (result) DBUG_RETURN(1); @@ -5621,6 +5663,7 @@ int QUICK_INDEX_MERGE_SELECT::prepare_unique() /* ok, all row ids are in Unique */ result= unique->get(head); + delete unique; doing_pk_scan= FALSE; /* start table scan */ init_read_record(&read_record, thd, head, (SQL_SELECT*) 0, 1, 1); @@ -5660,6 +5703,7 @@ int QUICK_INDEX_MERGE_SELECT::get_next() doing_pk_scan= TRUE; if ((result= pk_quick_select->init())) DBUG_RETURN(result); + pk_quick_select->reset(); DBUG_RETURN(pk_quick_select->get_next()); } } diff --git a/sql/opt_range.h b/sql/opt_range.h index 19234f61ea2..74d388128c8 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -127,7 +127,8 @@ public: reset() should be called when it is certain that row retrieval will be necessary. This call may do heavyweight initialization like buffering first N records etc. If reset() call fails get_next() must not be called. - + Note that reset() may be called several times if this quick select + executes in a subselect. RETURN 0 OK other Error code @@ -274,6 +275,10 @@ public: next=0; range= NULL; cur_range= NULL; + /* + Note: in opt_range.cc there are places where it is assumed that this + function always succeeds + */ return 0; } int init(); @@ -388,21 +393,15 @@ public: /* range quick selects this index_merge read consists of */ List quick_selects; - /* quick select which is currently used for rows retrieval */ - List_iterator_fast cur_quick_it; - QUICK_RANGE_SELECT* cur_quick_select; - /* quick select that uses clustered primary key (NULL if none) */ QUICK_RANGE_SELECT* pk_quick_select; /* true if this select is currently doing a clustered PK scan */ bool doing_pk_scan; - Unique *unique; MEM_ROOT alloc; - THD *thd; - int prepare_unique(); + int read_keys_and_merge(); /* used to get rows collected in Unique */ READ_RECORD read_record; @@ -465,6 +464,8 @@ public: MEM_ROOT alloc; /* Memory pool for this and merged quick selects data. */ THD *thd; /* current thread */ bool need_to_fetch_row; /* if true, do retrieve full table records. */ + /* in top-level quick select, true if merged scans where initialized */ + bool scans_inited; }; @@ -514,6 +515,7 @@ public: uint rowid_length; /* table rowid length */ private: static int queue_cmp(void *arg, byte *val1, byte *val2); + bool scans_inited; };