diff --git a/BitKeeper/etc/logging_ok b/BitKeeper/etc/logging_ok index 6ed0abe98fbb2b054b6690fbc4420b2b6215ae5f..c2609f031a2f94efbddbc9906a674a328f407051 100644 --- a/BitKeeper/etc/logging_ok +++ b/BitKeeper/etc/logging_ok @@ -103,6 +103,7 @@ peter@mysql.com peterg@mysql.com pgulutzan@linux.local pmartin@build.mysql2.com +psergey@psergey.(none) ram@deer.(none) ram@gw.mysql.r18.ru ram@gw.udmsearch.izhnet.ru diff --git a/mysql-test/r/index_merge.result b/mysql-test/r/index_merge.result new file mode 100644 index 0000000000000000000000000000000000000000..5b0c2ffd7a26818ef88704c8c7b9225c472912c1 --- /dev/null +++ b/mysql-test/r/index_merge.result @@ -0,0 +1,312 @@ +drop table if exists t0, t1, t2, t3; +create table t0 +( +key1 int not null, +INDEX i1(key1), +); +alter table t0 add key2 int not null, add index i2(key2); +alter table t0 add key3 int not null, add index i3(key3); +alter table t0 add key4 int not null, add index i4(key4); +alter table t0 add key5 int not null, add index i5(key5); +alter table t0 add key6 int not null, add index i6(key6); +alter table t0 add key7 int not null, add index i7(key7); +alter table t0 add key8 int not null, add index i8(key8); +update t0 set key2=key1,key3=key1,key4=key1,key5=key1,key6=key1,key7=key1,key8=1024-key1; +analyze table t0; +Table Op Msg_type Msg_text +test.t0 analyze status OK +explain select * from t0 where key1 < 3 or key1 > 1020; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 range i1 i1 4 NULL 55 Using where +explain +select * from t0 where key1 < 3 or key2 > 1020; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 31 Using where +select * from t0 where key1 < 3 or key2 > 1020; +key1 key2 key3 key4 key5 key6 key7 key8 +1 1 1 1 1 1 1 1023 +2 2 2 2 2 2 2 1022 +1021 1021 1021 1021 1021 1021 1021 3 +1022 1022 1022 1022 1022 1022 1022 2 +1023 1023 1023 1023 1023 1023 1023 1 +1024 1024 1024 1024 1024 1024 1024 0 +explain select * from t0 where key1 < 3 or key2 <4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 7 Using where +explain +select * from t0 where (key1 > 30 and key1<35) or (key2 >32 and key2 < 40); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 9 Using where +select * from t0 where (key1 > 30 and key1<35) or (key2 >32 and key2 < 40); +key1 key2 key3 key4 key5 key6 key7 key8 +31 31 31 31 31 31 31 993 +32 32 32 32 32 32 32 992 +33 33 33 33 33 33 33 991 +34 34 34 34 34 34 34 990 +35 35 35 35 35 35 35 989 +36 36 36 36 36 36 36 988 +37 37 37 37 37 37 37 987 +38 38 38 38 38 38 38 986 +39 39 39 39 39 39 39 985 +explain select * from t0 ignore index (i2) where key1 < 3 or key2 <4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL i1 NULL NULL NULL 1024 Using where +explain select * from t0 where (key1 < 3 or key2 <4) and key3 = 50; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ref i1,i2,i3 i3 4 const 1 Using where +explain select * from t0 use index (i1,i2) where (key1 < 3 or key2 <4) and key3 = 50; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 7 Using where +explain select * from t0 where (key1 > 1 or key2 > 2); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL i1,i2 NULL NULL NULL 1024 Using where +explain select * from t0 force index (i1,i2) where (key1 > 1 or key2 > 2); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 1024 Using where +explain +select * from t0 where key1<3 or key2<3 or (key1>5 and key1<8) or +(key1>10 and key1<12) or (key2>100 and key2<110); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 17 Using where +explain select * from t0 where key2 = 45 or key1 <=> null; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 range i1,i2 i2 4 NULL 1 Using where +explain select * from t0 where key2 = 45 or key1 is not null; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL i1,i2 NULL NULL NULL 1024 Using where +explain select * from t0 where key2 = 45 or key1 is null; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL i1,i2 NULL NULL NULL 1024 Using where +explain select * from t0 where key2=10 or key3=3 or key4 <=> null; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i2,i3,i4 i2,i3 4,4 NULL 2 Using where +explain select * from t0 where key2=10 or key3=3 or key4 is null; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL i2,i3,i4 NULL NULL NULL 1024 Using where +explain select key1 from t0 where (key1 <=> null) or (key2 < 5) or +(key3=10) or (key4 <=> null); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3,i4 i2,i3 4,4 NULL 6 Using where +explain select key1 from t0 where (key1 <=> null) or (key1 < 5) or +(key3=10) or (key4 <=> null); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i3,i4 i1,i3 4,4 NULL 5 Using where +explain select * from t0 where +(key1 < 3 or key2 < 3) and (key3 < 4 or key4 < 4) and (key5 < 5 or key6 < 5); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3,i4,i5,i6 i1,i2 4,4 NULL 6 Using where +explain +select * from t0 where (key1 < 3 or key2 < 6) and (key1 < 7 or key3 < 4); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3 i1,i2 4,4 NULL 9 Using where +select * from t0 where (key1 < 3 or key2 < 6) and (key1 < 7 or key3 < 4); +key1 key2 key3 key4 key5 key6 key7 key8 +1 1 1 1 1 1 1 1023 +2 2 2 2 2 2 2 1022 +3 3 3 3 3 3 3 1021 +4 4 4 4 4 4 4 1020 +5 5 5 5 5 5 5 1019 +explain select * from t0 where +(key1 < 3 or key2 < 3) and (key3 < 4 or key4 < 4) and (key5 < 2 or key6 < 2); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3,i4,i5,i6 i5,i6 4,4 NULL 4 Using where +explain select * from t0 where +(key1 < 3 or key2 < 3) and (key3 < 100); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 range i1,i2,i3 i3 4 NULL 96 Using where +explain select * from t0 where +(key1 < 3 or key2 < 3) and (key3 < 1000); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL i1,i2,i3 NULL NULL NULL 1024 Using where +explain select * from t0 where +((key1 < 4 or key2 < 4) and (key2 <5 or key3 < 4)) +or +key2 > 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL i1,i2,i3 NULL NULL NULL 1024 Using where +explain select * from t0 where +((key1 < 4 or key2 < 4) and (key2 <5 or key3 < 4)) +or +key1 < 7; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3 i1,i2 4,4 NULL 10 Using where +select * from t0 where +((key1 < 4 or key2 < 4) and (key2 <5 or key3 < 4)) +or +key1 < 7; +key1 key2 key3 key4 key5 key6 key7 key8 +1 1 1 1 1 1 1 1023 +2 2 2 2 2 2 2 1022 +3 3 3 3 3 3 3 1021 +4 4 4 4 4 4 4 1020 +5 5 5 5 5 5 5 1019 +6 6 6 6 6 6 6 1018 +explain select * from t0 where +((key1 < 4 or key2 < 4) and (key3 <5 or key5 < 4)) +or +((key5 < 5 or key6 < 6) and (key7 <7 or key8 < 4)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3,i5,i6,i7,i8 i1,i2,i5,i6 4,4,4,4 NULL 19 Using where +explain select * from t0 where +((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) +or +((key7 <7 or key8 < 4) and (key5 < 5 or key6 < 6)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3,i5,i6,i7,i8 i3,i5,i7,i8 4,4,4,4 NULL 21 Using where +explain select * from t0 where +((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) +or +((key3 <7 or key5 < 2) and (key5 < 5 or key6 < 6)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3,i5,i6 i3,i5 4,4 NULL 11 Using where +explain select * from t0 where +((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) +or +(((key3 <7 and key7 < 6) or key5 < 2) and (key5 < 5 or key6 < 6)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3,i5,i6,i7 i3,i5 4,4 NULL 11 Using where +explain select * from t0 where +((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) +or +((key3 >=5 or key5 < 2) and (key5 < 5 or key6 < 6)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL i1,i2,i3,i5,i6 NULL NULL NULL 1024 Using where +explain select * from t0 force index(i1, i2, i3, i4, i5, i6 ) where +((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) +or +((key3 >=5 or key5 < 2) and (key5 < 5 or key6 < 6)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2,i3,i5,i6 i3,i5 0,4 NULL 1024 Using where +select * from t0 where key1 < 5 or key8 < 4 order by key1; +key1 key2 key3 key4 key5 key6 key7 key8 +1 1 1 1 1 1 1 1023 +2 2 2 2 2 2 2 1022 +3 3 3 3 3 3 3 1021 +4 4 4 4 4 4 4 1020 +1021 1021 1021 1021 1021 1021 1021 3 +1022 1022 1022 1022 1022 1022 1022 2 +1023 1023 1023 1023 1023 1023 1023 1 +1024 1024 1024 1024 1024 1024 1024 0 +explain +select * from t0 where key1 < 5 or key8 < 4 order by key1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i8 i1,i8 4,4 NULL 9 Using where; Using filesort +create table t2 like t0; +insert into t2 select * from t0; +alter table t2 add index i1_3(key1, key3); +alter table t2 add index i2_3(key2, key3); +alter table t2 drop index i1; +alter table t2 drop index i2; +alter table t2 add index i321(key3, key2, key1); +explain select key3 from t2 where key1 = 100 or key2 = 100; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index_merge i1_3,i2_3 i1_3,i2_3 4,4 NULL 2 Using where +explain select key3 from t2 where key1 <100 or key2 < 100; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index i1_3,i2_3 i321 12 NULL 1024 Using where; Using index +explain select key7 from t2 where key1 <100 or key2 < 100; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL i1_3,i2_3 NULL NULL NULL 1024 Using where +create table t4 ( +key1a int not null, +key1b int not null, +key2 int not null, +key2_1 int not null, +key2_2 int not null, +key3 int not null, +index i1a (key1a, key1b), +index i1b (key1b, key1a), +index i2_1(key2, key2_1), +index i2_2(key2, key2_1), +); +insert into t4 select key1,key1,key1 div 10, key1 % 10, key1 % 10, key1 from t0; +select * from t4 where key1a = 3 or key1b = 4; +key1a key1b key2 key2_1 key2_2 key3 +3 3 0 3 3 3 +4 4 0 4 4 4 +explain select * from t4 where key1a = 3 or key1b = 4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t4 index_merge i1a,i1b i1a,i1b 4,4 NULL 2 Using where +explain select * from t4 where key2 = 1 and (key2_1 = 1 or key3 = 5); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t4 ref i2_1,i2_2 i2_1 4 const 10 Using where +explain select * from t4 where key2 = 1 and (key2_1 = 1 or key2_2 = 5); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t4 ref i2_1,i2_2 i2_1 4 const 10 Using where +explain select * from t4 where key2_1 = 1 or key2_2 = 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t4 ALL NULL NULL NULL NULL 1024 Using where +create table t1 like t0; +insert into t1 select * from t0; +explain select * from t0 left join t1 on (t0.key1=t1.key1) +where t0.key1=3 or t0.key2=4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 2 Using where +1 SIMPLE t1 ref i1 i1 4 test.t0.key1 1 +select * from t0 left join t1 on (t0.key1=t1.key1) +where t0.key1=3 or t0.key2=4; +key1 key2 key3 key4 key5 key6 key7 key8 key1 key2 key3 key4 key5 key6 key7 key8 +3 3 3 3 3 3 3 1021 3 3 3 3 3 3 3 1021 +4 4 4 4 4 4 4 1020 4 4 4 4 4 4 4 1020 +explain +select * from t0,t1 where (t0.key1=t1.key1) and ( t0.key1=3 or t0.key2=4); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 2 Using where +1 SIMPLE t1 ref i1 i1 4 test.t0.key1 1 +explain +select * from t0,t1 where (t0.key1=t1.key1) and +(t0.key1=3 or t0.key2=4) and t1.key1<200; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 index_merge i1,i2 i1,i2 4,4 NULL 2 Using where +1 SIMPLE t1 ref i1 i1 4 test.t0.key1 1 Using where +explain +select * from t0,t1 where (t0.key1=t1.key1) and +(t0.key1=3 or t0.key2<4) and t1.key1=2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ref i1,i2 i1 4 const 1 Using where +1 SIMPLE t1 ref i1 i1 4 const 1 Using where +explain select * from t0,t1 where t0.key1 = 5 and +(t1.key1 = t0.key1 or t1.key8 = t0.key1); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ref i1 i1 4 const 1 Using where +1 SIMPLE t1 index_merge i1,i8 i1,i8 4,4 NULL 2 Using where +explain select * from t1 where key1=3 or key2=4 +union select * from t1 where key1<4 or key3=5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index_merge i1,i2 i1,i2 4,4 NULL 2 Using where +2 UNION t1 index_merge i1,i3 i1,i3 4,4 NULL 5 Using where +explain select * from (select * from t1 where key1 = 3 or key2 =3) as Z where key8 >5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY <derived2> system NULL NULL NULL NULL 1 +2 DERIVED t1 index_merge i1,i2 i1,i2 4,4 NULL 2 Using where +create table t3 like t0; +insert into t3 select * from t0; +alter table t3 add key9 int not null, add index i9(key9); +alter table t3 add keyA int not null, add index iA(keyA); +alter table t3 add keyB int not null, add index iB(keyB); +alter table t3 add keyC int not null, add index iC(keyC); +update t3 set key9=key1,keyA=key1,keyB=key1,keyC=key1; +explain select * from t3 where +key1=1 or key2=2 or key3=3 or key4=4 or +key5=5 or key6=6 or key7=7 or key8=8 or +key9=9 or keyA=10 or keyB=11 or keyC=12; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t3 index_merge i1,i2,i3,i4,i5,i6,i7,i8,i9,iA,iB,iC i1,i2,i3,i4,i5,i6,i7,i8,i9,iA,iB,iC 4,4,4,4,4,4,4,4,4,4,4,4 NULL 12 Using where +select * from t3 where +key1=1 or key2=2 or key3=3 or key4=4 or +key5=5 or key6=6 or key7=7 or key8=8 or +key9=9 or keyA=10 or keyB=11 or keyC=12; +key1 key2 key3 key4 key5 key6 key7 key8 key9 keyA keyB keyC +1 1 1 1 1 1 1 1023 1 1 1 1 +2 2 2 2 2 2 2 1022 2 2 2 2 +3 3 3 3 3 3 3 1021 3 3 3 3 +4 4 4 4 4 4 4 1020 4 4 4 4 +5 5 5 5 5 5 5 1019 5 5 5 5 +6 6 6 6 6 6 6 1018 6 6 6 6 +7 7 7 7 7 7 7 1017 7 7 7 7 +1016 1016 1016 1016 1016 1016 1016 8 1016 1016 1016 1016 +9 9 9 9 9 9 9 1015 9 9 9 9 +10 10 10 10 10 10 10 1014 10 10 10 10 +11 11 11 11 11 11 11 1013 11 11 11 11 +12 12 12 12 12 12 12 1012 12 12 12 12 +drop table t0, t1, t2, t3, t4; diff --git a/mysql-test/r/index_merge_innodb.result b/mysql-test/r/index_merge_innodb.result new file mode 100644 index 0000000000000000000000000000000000000000..ef6ffa82da7aaaa4ba43195cb3eda0a118502f3e --- /dev/null +++ b/mysql-test/r/index_merge_innodb.result @@ -0,0 +1,55 @@ +drop table if exists t1; +create table t1 +( +key1 int not null, +key2 int not null, +INDEX i1(key1), +INDEX i2(key2), +) type=innodb; +explain select * from t1 where key1 < 5 or key2 > 197; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge i1,i2 i1,i2 4,4 NULL 8 Using where +select * from t1 where key1 < 5 or key2 > 197; +key1 key2 +0 200 +1 199 +2 198 +3 197 +4 196 +explain select * from t1 where key1 < 3 or key2 > 195; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge i1,i2 i1,i2 4,4 NULL 8 Using where +select * from t1 where key1 < 3 or key2 > 195; +key1 key2 +0 200 +1 199 +2 198 +4 196 +3 197 +alter table t1 add str1 char (255) not null, +add zeroval int not null default 0, +add str2 char (255) not null, +add str3 char (255) not null; +update t1 set str1='aaa', str2='bbb', str3=concat(key2, '-', key1 div 2, '_' ,if(key1 mod 2 = 0, 'a', 'A')); +alter table t1 add primary key (str1, zeroval, str2, str3); +explain select * from t1 where key1 < 5 or key2 > 197; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge i1,i2 i1,i2 4,4 NULL 8 Using where +select * from t1 where key1 < 5 or key2 > 197; +key1 key2 str1 zeroval str2 str3 +0 200 aaa 0 bbb 200-0_a +1 199 aaa 0 bbb 199-0_A +2 198 aaa 0 bbb 198-1_a +3 197 aaa 0 bbb 197-1_A +4 196 aaa 0 bbb 196-2_a +explain select * from t1 where key1 < 3 or key2 > 195; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge i1,i2 i1,i2 4,4 NULL 8 Using where +select * from t1 where key1 < 3 or key2 > 195; +key1 key2 str1 zeroval str2 str3 +0 200 aaa 0 bbb 200-0_a +1 199 aaa 0 bbb 199-0_A +2 198 aaa 0 bbb 198-1_a +4 196 aaa 0 bbb 196-2_a +3 197 aaa 0 bbb 197-1_A +drop table t1; diff --git a/mysql-test/t/index_merge.test b/mysql-test/t/index_merge.test new file mode 100644 index 0000000000000000000000000000000000000000..793ae848c526075efd1d950b61588163ed3801f4 --- /dev/null +++ b/mysql-test/t/index_merge.test @@ -0,0 +1,267 @@ +# +# Index merge tests +# + +--disable_warnings +drop table if exists t0, t1, t2, t3; +--enable_warnings + +# Create and fill a table with simple keys +create table t0 +( + key1 int not null, + INDEX i1(key1), +); + +--disable_query_log +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); + +let $1=7; +set @d=8; +while ($1) +{ + eval insert into t0 select key1+@d from t0; + eval set @d=@d*2; + dec $1; +} +--enable_query_log + +alter table t0 add key2 int not null, add index i2(key2); +alter table t0 add key3 int not null, add index i3(key3); +alter table t0 add key4 int not null, add index i4(key4); +alter table t0 add key5 int not null, add index i5(key5); +alter table t0 add key6 int not null, add index i6(key6); +alter table t0 add key7 int not null, add index i7(key7); +alter table t0 add key8 int not null, add index i8(key8); + +update t0 set key2=key1,key3=key1,key4=key1,key5=key1,key6=key1,key7=key1,key8=1024-key1; +analyze table t0; + +# 1. One index +explain select * from t0 where key1 < 3 or key1 > 1020; + +# 2. Simple cases +explain +select * from t0 where key1 < 3 or key2 > 1020; +select * from t0 where key1 < 3 or key2 > 1020; + +explain select * from t0 where key1 < 3 or key2 <4; + +explain +select * from t0 where (key1 > 30 and key1<35) or (key2 >32 and key2 < 40); +select * from t0 where (key1 > 30 and key1<35) or (key2 >32 and key2 < 40); + +# 3. Check that index_merge doesn't break "ignore/force/use index" +explain select * from t0 ignore index (i2) where key1 < 3 or key2 <4; +explain select * from t0 where (key1 < 3 or key2 <4) and key3 = 50; +explain select * from t0 use index (i1,i2) where (key1 < 3 or key2 <4) and key3 = 50; + +explain select * from t0 where (key1 > 1 or key2 > 2); +explain select * from t0 force index (i1,i2) where (key1 > 1 or key2 > 2); + + +# 4. Check if conjuncts are grouped by keyuse +explain + select * from t0 where key1<3 or key2<3 or (key1>5 and key1<8) or + (key1>10 and key1<12) or (key2>100 and key2<110); + +# 5. Check index_merge with conjuncts that are always true/false +# verify fallback to "range" if there is only one non-confluent condition +explain select * from t0 where key2 = 45 or key1 <=> null; + +explain select * from t0 where key2 = 45 or key1 is not null; +explain select * from t0 where key2 = 45 or key1 is null; + +# the last conj. is always false and will be discarded +explain select * from t0 where key2=10 or key3=3 or key4 <=> null; + +# the last conj. is always true and will cause 'all' scan +explain select * from t0 where key2=10 or key3=3 or key4 is null; + +# some more complicated cases +explain select key1 from t0 where (key1 <=> null) or (key2 < 5) or + (key3=10) or (key4 <=> null); +explain select key1 from t0 where (key1 <=> null) or (key1 < 5) or + (key3=10) or (key4 <=> null); + +# 6.Several ways to do index_merge, (ignored) index_merge vs. range +explain select * from t0 where + (key1 < 3 or key2 < 3) and (key3 < 4 or key4 < 4) and (key5 < 5 or key6 < 5); + +explain +select * from t0 where (key1 < 3 or key2 < 6) and (key1 < 7 or key3 < 4); + +select * from t0 where (key1 < 3 or key2 < 6) and (key1 < 7 or key3 < 4); + + +explain select * from t0 where + (key1 < 3 or key2 < 3) and (key3 < 4 or key4 < 4) and (key5 < 2 or key6 < 2); + +# now index_merge is not used at all when "range" is possible +explain select * from t0 where + (key1 < 3 or key2 < 3) and (key3 < 100); + +# this even can cause "all" scan: +explain select * from t0 where + (key1 < 3 or key2 < 3) and (key3 < 1000); + + +# 7. Complex cases +# tree_or(List<SEL_IMERGE>, range SEL_TREE). +explain select * from t0 where + ((key1 < 4 or key2 < 4) and (key2 <5 or key3 < 4)) + or + key2 > 5; + +explain select * from t0 where + ((key1 < 4 or key2 < 4) and (key2 <5 or key3 < 4)) + or + key1 < 7; + +select * from t0 where + ((key1 < 4 or key2 < 4) and (key2 <5 or key3 < 4)) + or + key1 < 7; + +# tree_or(List<SEL_IMERGE>, List<SEL_IMERGE>). +explain select * from t0 where + ((key1 < 4 or key2 < 4) and (key3 <5 or key5 < 4)) + or + ((key5 < 5 or key6 < 6) and (key7 <7 or key8 < 4)); + +explain select * from t0 where + ((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) + or + ((key7 <7 or key8 < 4) and (key5 < 5 or key6 < 6)); + +explain select * from t0 where + ((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) + or + ((key3 <7 or key5 < 2) and (key5 < 5 or key6 < 6)); + +explain select * from t0 where + ((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) + or + (((key3 <7 and key7 < 6) or key5 < 2) and (key5 < 5 or key6 < 6)); + +explain select * from t0 where + ((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) + or + ((key3 >=5 or key5 < 2) and (key5 < 5 or key6 < 6)); + +explain select * from t0 force index(i1, i2, i3, i4, i5, i6 ) where + ((key3 <5 or key5 < 4) and (key1 < 4 or key2 < 4)) + or + ((key3 >=5 or key5 < 2) and (key5 < 5 or key6 < 6)); + +# 8. Verify that "order by" after index merge uses filesort +select * from t0 where key1 < 5 or key8 < 4 order by key1; + +explain +select * from t0 where key1 < 5 or key8 < 4 order by key1; + +# 9. Check that index_merge cost is compared to 'index' where possible +create table t2 like t0; +insert into t2 select * from t0; + +alter table t2 add index i1_3(key1, key3); +alter table t2 add index i2_3(key2, key3); +alter table t2 drop index i1; +alter table t2 drop index i2; +alter table t2 add index i321(key3, key2, key1); + +# index_merge vs 'index', index_merge is better. +explain select key3 from t2 where key1 = 100 or key2 = 100; + +# index_merge vs 'index', 'index' is better. +explain select key3 from t2 where key1 <100 or key2 < 100; + +# index_merge vs 'all', index_merge is better. +explain select key7 from t2 where key1 <100 or key2 < 100; + +# 10. Multipart keys. +create table t4 ( + key1a int not null, + key1b int not null, + key2 int not null, + key2_1 int not null, + key2_2 int not null, + key3 int not null, + + index i1a (key1a, key1b), + index i1b (key1b, key1a), + + index i2_1(key2, key2_1), + index i2_2(key2, key2_1), +); + +insert into t4 select key1,key1,key1 div 10, key1 % 10, key1 % 10, key1 from t0; + +# the following will be handled by index_merge: +select * from t4 where key1a = 3 or key1b = 4; +explain select * from t4 where key1a = 3 or key1b = 4; + +# and the following will not +explain select * from t4 where key2 = 1 and (key2_1 = 1 or key3 = 5); + +explain select * from t4 where key2 = 1 and (key2_1 = 1 or key2_2 = 5); + +explain select * from t4 where key2_1 = 1 or key2_2 = 5; + + +# 11. Multitable selects +create table t1 like t0; +insert into t1 select * from t0; + +# index_merge on first table in join +explain select * from t0 left join t1 on (t0.key1=t1.key1) + where t0.key1=3 or t0.key2=4; + +select * from t0 left join t1 on (t0.key1=t1.key1) + where t0.key1=3 or t0.key2=4; + +explain +select * from t0,t1 where (t0.key1=t1.key1) and ( t0.key1=3 or t0.key2=4); + +# index_merge vs. ref +explain +select * from t0,t1 where (t0.key1=t1.key1) and + (t0.key1=3 or t0.key2=4) and t1.key1<200; + +# index_merge vs. ref +explain +select * from t0,t1 where (t0.key1=t1.key1) and + (t0.key1=3 or t0.key2<4) and t1.key1=2; + +# index_merge on second table in join +explain select * from t0,t1 where t0.key1 = 5 and + (t1.key1 = t0.key1 or t1.key8 = t0.key1); + +# index_merge inside union +explain select * from t1 where key1=3 or key2=4 + union select * from t1 where key1<4 or key3=5; + +# index merge in subselect +explain select * from (select * from t1 where key1 = 3 or key2 =3) as Z where key8 >5; + +# 12. check for long index_merges. +create table t3 like t0; +insert into t3 select * from t0; +alter table t3 add key9 int not null, add index i9(key9); +alter table t3 add keyA int not null, add index iA(keyA); +alter table t3 add keyB int not null, add index iB(keyB); +alter table t3 add keyC int not null, add index iC(keyC); +update t3 set key9=key1,keyA=key1,keyB=key1,keyC=key1; + +explain select * from t3 where + key1=1 or key2=2 or key3=3 or key4=4 or + key5=5 or key6=6 or key7=7 or key8=8 or + key9=9 or keyA=10 or keyB=11 or keyC=12; + +select * from t3 where + key1=1 or key2=2 or key3=3 or key4=4 or + key5=5 or key6=6 or key7=7 or key8=8 or + key9=9 or keyA=10 or keyB=11 or keyC=12; + + +drop table t0, t1, t2, t3, t4; diff --git a/mysql-test/t/index_merge_innodb.test b/mysql-test/t/index_merge_innodb.test new file mode 100644 index 0000000000000000000000000000000000000000..d3943918468c93ad72b6378b8651e8d3ae492fde --- /dev/null +++ b/mysql-test/t/index_merge_innodb.test @@ -0,0 +1,54 @@ +# +# Index merge tests +# +-- source include/have_innodb.inc + +--disable_warnings +drop table if exists t1; +--enable_warnings + +create table t1 +( + key1 int not null, + key2 int not null, + + INDEX i1(key1), + INDEX i2(key2), +) type=innodb; + +--disable_query_log +let $1=200; +while ($1) +{ + eval insert into t1 values (200-$1, $1); + dec $1; +} +--enable_query_log + +# No primary key +explain select * from t1 where key1 < 5 or key2 > 197; + +select * from t1 where key1 < 5 or key2 > 197; + +explain select * from t1 where key1 < 3 or key2 > 195; +select * from t1 where key1 < 3 or key2 > 195; + +# Primary key as case-sensitive string with \0s. +# also make primary key be longer then max. index length of MyISAM. +alter table t1 add str1 char (255) not null, + add zeroval int not null default 0, + add str2 char (255) not null, + add str3 char (255) not null; + +update t1 set str1='aaa', str2='bbb', str3=concat(key2, '-', key1 div 2, '_' ,if(key1 mod 2 = 0, 'a', 'A')); + +alter table t1 add primary key (str1, zeroval, str2, str3); + +explain select * from t1 where key1 < 5 or key2 > 197; + +select * from t1 where key1 < 5 or key2 > 197; + +explain select * from t1 where key1 < 3 or key2 > 195; +select * from t1 where key1 < 3 or key2 > 195; + +drop table t1; diff --git a/sql/opt_ft.cc b/sql/opt_ft.cc index 74349819937dfe63fdf54145dd8ecaef375c3bc2..9d1fc55c777c7472783d2c2a1a17e24b978dc378 100644 --- a/sql/opt_ft.cc +++ b/sql/opt_ft.cc @@ -26,7 +26,7 @@ ** Create a FT or QUICK RANGE based on a key ****************************************************************************/ -QUICK_SELECT *get_ft_or_quick_select_for_ref(THD *thd, TABLE *table, +QUICK_RANGE_SELECT *get_ft_or_quick_select_for_ref(THD *thd, TABLE *table, JOIN_TAB *tab) { if (tab->type == JT_FT) diff --git a/sql/opt_ft.h b/sql/opt_ft.h index 69b6b72f3fc440e24dcbeb26149e053fa7a4b153..954c25b6caae02bcac84e6f77bf17f6f268cf7e1 100644 --- a/sql/opt_ft.h +++ b/sql/opt_ft.h @@ -24,18 +24,19 @@ #pragma interface /* gcc class implementation */ #endif -class FT_SELECT: public QUICK_SELECT { +class FT_SELECT: public QUICK_RANGE_SELECT { public: TABLE_REF *ref; FT_SELECT(THD *thd, TABLE *table, TABLE_REF *tref) : - QUICK_SELECT (thd, table, tref->key, 1), ref(tref) { init(); } + QUICK_RANGE_SELECT (thd, table, tref->key, 1), ref(tref) { init(); } int init() { return error=file->ft_init(); } int get_next() { return error=file->ft_read(record); } + int get_type() { return QS_TYPE_FULLTEXT; } }; -QUICK_SELECT *get_ft_or_quick_select_for_ref(THD *thd, TABLE *table, +QUICK_RANGE_SELECT *get_ft_or_quick_select_for_ref(THD *thd, TABLE *table, JOIN_TAB *tab); #endif diff --git a/sql/opt_range.cc b/sql/opt_range.cc index e2761832e657fb64a86e3a780f03ff1eed740e2f..c3fa0139c209d07ca079a834f081e68be0827ad5 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -267,14 +267,17 @@ class SEL_ARG :public Sql_alloc SEL_ARG *clone_tree(); }; +class SEL_IMERGE; class SEL_TREE :public Sql_alloc { public: enum Type { IMPOSSIBLE, ALWAYS, MAYBE, KEY, KEY_SMALLER } type; SEL_TREE(enum Type type_arg) :type(type_arg) {} - SEL_TREE() :type(KEY) { bzero((char*) keys,sizeof(keys));} + SEL_TREE() :type(KEY), keys_map(0) { bzero((char*) keys,sizeof(keys));} SEL_ARG *keys[MAX_KEY]; + key_map keys_map; /* bitmask of non-NULL elements in keys */ + List<SEL_IMERGE> merges; /* possible ways to read rows using index_merge */ }; @@ -302,10 +305,19 @@ static ha_rows check_quick_keys(PARAM *param,uint index,SEL_ARG *key_tree, char *min_key,uint min_key_flag, char *max_key, uint max_key_flag); -static QUICK_SELECT *get_quick_select(PARAM *param,uint index, - SEL_ARG *key_tree); +QUICK_RANGE_SELECT *get_quick_select(PARAM *param,uint index, + SEL_ARG *key_tree, MEM_ROOT *alloc = NULL); +static int get_quick_select_params(SEL_TREE *tree, PARAM& param, + key_map& needed_reg, TABLE *head, + bool index_read_can_be_used, + double* read_time, + ha_rows* records, + SEL_ARG*** key_to_read); #ifndef DBUG_OFF -static void print_quick(QUICK_SELECT *quick,const key_map* needed_reg); +void print_quick_sel_imerge(QUICK_INDEX_MERGE_SELECT *quick, + const key_map *needed_reg); +void print_quick_sel_range(QUICK_RANGE_SELECT *quick, const key_map *needed_reg); + #endif static SEL_TREE *tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2); static SEL_TREE *tree_or(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2); @@ -313,16 +325,234 @@ static SEL_ARG *sel_add(SEL_ARG *key1,SEL_ARG *key2); static SEL_ARG *key_or(SEL_ARG *key1,SEL_ARG *key2); static SEL_ARG *key_and(SEL_ARG *key1,SEL_ARG *key2,uint clone_flag); static bool get_range(SEL_ARG **e1,SEL_ARG **e2,SEL_ARG *root1); -static bool get_quick_keys(PARAM *param,QUICK_SELECT *quick,KEY_PART *key, +bool get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key, SEL_ARG *key_tree,char *min_key,uint min_key_flag, char *max_key,uint max_key_flag); static bool eq_tree(SEL_ARG* a,SEL_ARG *b); static SEL_ARG null_element(SEL_ARG::IMPOSSIBLE); static bool null_part_in_key(KEY_PART *key_part, const char *key, uint length); +bool sel_trees_can_be_ored(SEL_TREE *tree1, SEL_TREE *tree2, PARAM* param); + + +/* + SEL_IMERGE is a list of possible ways to do index merge, i.e. it is + a condition in the following form: + (t_1||t_2||...||t_N) && (next) + + where all t_i are SEL_TREEs, next is another SEL_IMERGE and no pair + (t_i,t_j) contains SEL_ARGS for the same index. + + SEL_TREE contained in SEL_IMERGE always has merges=NULL. + + This class relies on memory manager to do the cleanup. +*/ + +class SEL_IMERGE : public Sql_alloc +{ + enum { PREALLOCED_TREES= 10}; +public: + SEL_TREE *trees_prealloced[PREALLOCED_TREES]; + SEL_TREE **trees; /* trees used to do index_merge */ + SEL_TREE **trees_next; /* last of these trees */ + SEL_TREE **trees_end; /* end of allocated space */ + + SEL_ARG ***best_keys; /* best keys to read in SEL_TREEs */ + + SEL_IMERGE() : + trees(&trees_prealloced[0]), + trees_next(trees), + trees_end(trees + PREALLOCED_TREES) + {} + int or_sel_tree(PARAM *param, SEL_TREE *tree); + int or_sel_tree_with_checks(PARAM *param, SEL_TREE *new_tree); + int or_sel_imerge_with_checks(PARAM *param, SEL_IMERGE* imerge); +}; + + +/* + Add SEL_TREE to this index_merge without any checks, + + NOTES + This function implements the following: + (x_1||...||x_N) || t = (x_1||...||x_N||t), where x_i, t are SEL_TREEs + + RETURN + 0 - OK + -1 - Out of memory. +*/ + +int SEL_IMERGE::or_sel_tree(PARAM *param, SEL_TREE *tree) +{ + if (trees_next == trees_end) + { + const int realloc_ratio= 2; /* Double size for next round */ + uint old_elements= (trees_end - trees); + uint old_size= sizeof(SEL_TREE**) * old_elements; + uint new_size= old_size * realloc_ratio; + SEL_TREE **new_trees; + if (!(new_trees= (SEL_TREE**)alloc_root(param->mem_root, new_size))) + return -1; + memcpy(new_trees, trees, old_size); + trees= new_trees; + trees_next= trees + old_elements; + trees_end= trees + old_elements * realloc_ratio; + } + *(trees_next++)= tree; + return 0; +} + + +/* + Perform OR operation on this SEL_IMERGE and supplied SEL_TREE new_tree, + combining new_tree with one of the trees in this SEL_IMERGE if they both + have SEL_ARGs for the same key. + + SYNOPSIS + or_sel_tree_with_checks() + param PARAM from SQL_SELECT::test_quick_select + new_tree SEL_TREE with type KEY or KEY_SMALLER. + + NOTES + This does the following: + (t_1||...||t_k)||new_tree = + either + = (t_1||...||t_k||new_tree) + or + = (t_1||....||(t_j|| new_tree)||...||t_k), + + where t_i, y are SEL_TREEs. + new_tree is combined with the first t_j it has a SEL_ARG on common + key with. As a consequence of this, choice of keys to do index_merge + read may depend on the order of conditions in WHERE part of the query. + + RETURN + 0 OK + 1 One of the trees was combined with new_tree to SEL_TREE::ALWAYS, + and (*this) should be discarded. + -1 An error occurred. +*/ + +int SEL_IMERGE::or_sel_tree_with_checks(PARAM *param, SEL_TREE *new_tree) +{ + for (SEL_TREE** tree = trees; + tree != trees_next; + tree++) + { + if (sel_trees_can_be_ored(*tree, new_tree, param)) + { + *tree = tree_or(param, *tree, new_tree); + if (!*tree) + return 1; + if (((*tree)->type == SEL_TREE::MAYBE) || + ((*tree)->type == SEL_TREE::ALWAYS)) + return 1; + /* SEL_TREE::IMPOSSIBLE is impossible here */ + return 0; + } + } + + /* new tree cannot be combined with any of existing trees */ + return or_sel_tree(param, new_tree); +} + + +/* + Perform OR operation on this index_merge and supplied index_merge list. + + RETURN + 0 - OK + 1 - One of conditions in result is always TRUE and this SEL_IMERGE + should be discarded. + -1 - An error occurred +*/ + +int SEL_IMERGE::or_sel_imerge_with_checks(PARAM *param, SEL_IMERGE* imerge) +{ + for (SEL_TREE** tree= imerge->trees; + tree != imerge->trees_next; + tree++) + { + if (or_sel_tree_with_checks(param, *tree)) + return 1; + } + return 0; +} + + +/* + Perform AND operation on two index_merge lists, storing result in *im1. + +*/ + +inline void imerge_list_and_list(List<SEL_IMERGE> *im1, List<SEL_IMERGE> *im2) +{ + im1->concat(im2); +} + + +/* + Perform OR operation on 2 index_merge lists, storing result in first list. + + NOTES + The following conversion is implemented: + (a_1 &&...&& a_N)||(b_1 &&...&& b_K) = AND_i,j(a_i || b_j) => + => (a_1||b_1). + + i.e. all conjuncts except the first one are currently dropped. + This is done to avoid producing N*K ways to do index_merge. + + If (a_1||b_1) produce a condition that is always true, NULL is + returned and index_merge is discarded. (while it is actually + possible to try harder). + + As a consequence of this, choice of keys to do index_merge + read may depend on the order of conditions in WHERE part of + the query. + + RETURN + 0 OK, result is stored in *im1 + other Error, both passed lists are unusable + +*/ + +int imerge_list_or_list(PARAM *param, + List<SEL_IMERGE> *im1, + List<SEL_IMERGE> *im2) +{ + SEL_IMERGE *imerge= im1->head(); + im1->empty(); + im1->push_back(imerge); + + return imerge->or_sel_imerge_with_checks(param, im2->head()); +} + + +/* + Perform OR operation on index_merge list and key tree. + + RETURN + 0 OK, result is stored in *im1 + other Error + +*/ + +int imerge_list_or_tree(PARAM *param, + List<SEL_IMERGE> *im1, + SEL_TREE *tree) +{ + SEL_IMERGE *imerge; + List_iterator<SEL_IMERGE> it(*im1); + while((imerge= it++)) + { + if (imerge->or_sel_tree_with_checks(param, tree)) + it.remove(); + } + return im1->is_empty(); +} /*************************************************************************** -** Basic functions for SQL_SELECT and QUICK_SELECT +** Basic functions for SQL_SELECT and QUICK_RANGE_SELECT ***************************************************************************/ /* make a select from mysql info @@ -379,11 +609,19 @@ SQL_SELECT::~SQL_SELECT() #undef index // Fix for Unixware 7 -QUICK_SELECT::QUICK_SELECT(THD *thd, TABLE *table, uint key_nr, bool no_alloc) - :dont_free(0),error(0),index(key_nr),max_used_key_length(0), - used_key_parts(0), head(table), it(ranges),range(0) +QUICK_SELECT_I::QUICK_SELECT_I() + :max_used_key_length(0), + used_key_parts(0) +{} + +QUICK_RANGE_SELECT::QUICK_RANGE_SELECT(THD *thd, TABLE *table, uint key_nr, + bool no_alloc, MEM_ROOT *parent_alloc) + :dont_free(0),error(0),it(ranges),range(0) { - if (!no_alloc) + index= key_nr; + head= table; + + if (!no_alloc && !parent_alloc) { // Allocates everything through the internal memroot init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); @@ -391,12 +629,16 @@ QUICK_SELECT::QUICK_SELECT(THD *thd, TABLE *table, uint key_nr, bool no_alloc) } else bzero((char*) &alloc,sizeof(alloc)); - file=head->file; - record=head->record[0]; - init(); + file= head->file; + record= head->record[0]; +} + +int QUICK_RANGE_SELECT::init() +{ + return (error= file->index_init(index)); } -QUICK_SELECT::~QUICK_SELECT() +QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT() { if (!dont_free) { @@ -405,6 +647,42 @@ QUICK_SELECT::~QUICK_SELECT() } } + +QUICK_INDEX_MERGE_SELECT::QUICK_INDEX_MERGE_SELECT(THD *thd, TABLE *table) + :cur_quick_it(quick_selects), index_merge(thd) +{ + index= MAX_KEY; + head= table; + init_sql_alloc(&alloc,1024,0); +} + +int QUICK_INDEX_MERGE_SELECT::init() +{ + int error; + cur_quick_it.rewind(); + cur_quick_select= cur_quick_it++; + if (error= index_merge.init(head)) + return error; + return cur_quick_select->init(); +} + +void QUICK_INDEX_MERGE_SELECT::reset() +{ + cur_quick_select->reset(); +} + +bool +QUICK_INDEX_MERGE_SELECT::push_quick_back(QUICK_RANGE_SELECT *quick_sel_range) +{ + return quick_selects.push_back(quick_sel_range); +} + +QUICK_INDEX_MERGE_SELECT::~QUICK_INDEX_MERGE_SELECT() +{ + quick_selects.delete_elements(); + free_root(&alloc,MYF(0)); +} + QUICK_RANGE::QUICK_RANGE() :min_key(0),max_key(0),min_length(0),max_length(0), flag(NO_MIN_RANGE | NO_MAX_RANGE) @@ -598,6 +876,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, uint basflag; uint idx; double scan_time; + QUICK_INDEX_MERGE_SELECT *quick_imerge; DBUG_ENTER("test_quick_select"); DBUG_PRINT("enter",("keys_to_use: %lu prev_tables: %lu const_tables: %lu", keys_to_use.to_ulonglong(), (ulong) prev_tables, @@ -691,70 +970,211 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, read_time= (double) HA_POS_ERROR; } else if (tree->type == SEL_TREE::KEY || - tree->type == SEL_TREE::KEY_SMALLER) + tree->type == SEL_TREE::KEY_SMALLER) { - SEL_ARG **key,**end,**best_key=0; - - - for (idx=0,key=tree->keys, end=key+param.keys ; - key != end ; - key++,idx++) - { - ha_rows found_records; - double found_read_time; - if (*key) - { - uint keynr= param.real_keynr[idx]; - if ((*key)->type == SEL_ARG::MAYBE_KEY || - (*key)->maybe_flag) - needed_reg.set_bit(keynr); - - found_records=check_quick_select(¶m, idx, *key); - if (found_records != HA_POS_ERROR && found_records > 2 && - head->used_keys.is_set(keynr) && - (head->file->index_flags(keynr) & HA_KEY_READ_ONLY)) - { - /* - We can resolve this by only reading through this key. - Assume that we will read trough the whole key range - and that all key blocks are half full (normally things are - much better). - */ - uint keys_per_block= (head->file->block_size/2/ - (head->key_info[keynr].key_length+ - head->file->ref_length) + 1); - found_read_time=((double) (found_records+keys_per_block-1)/ - (double) keys_per_block); - } - else - found_read_time= (head->file->read_time(keynr, - param.range_count, - found_records)+ - (double) found_records / TIME_FOR_COMPARE); - if (read_time > found_read_time && found_records != HA_POS_ERROR) - { - read_time=found_read_time; - records=found_records; - best_key=key; - } - } - } - if (best_key && records) - { - if ((quick=get_quick_select(¶m,(uint) (best_key-tree->keys), - *best_key))) - { - quick->records=records; - quick->read_time=read_time; - } - } + /* + It is possible to use a quick select (but maybe it would be slower + than 'all' table scan). + */ + SEL_ARG **best_key= 0; + ha_rows found_records; + double found_read_time= read_time; + + if (!get_quick_select_params(tree, param, needed_reg, head, true, + &found_read_time, &found_records, + &best_key)) + { + /* + Ok, quick select is better than 'all' table scan and we have its + parameters, so construct it. + */ + read_time= found_read_time; + records= found_records; + + if ((quick= get_quick_select(¶m,(uint) (best_key-tree->keys), + *best_key)) && (!quick->init())) + { + quick->records= records; + quick->read_time= read_time; + } + } + + /* + btw, tree type SEL_TREE::INDEX_MERGE was not introduced + intentionally + */ + + /* if no range select could be built, try using index_merge */ + if (!quick && !tree->merges.is_empty()) + { + DBUG_PRINT("info",("No range reads possible," + " trying to construct index_merge")); + SEL_IMERGE *imerge; + SEL_IMERGE *min_imerge= NULL; + double min_imerge_cost= DBL_MAX; + ha_rows min_imerge_records; + + List_iterator_fast<SEL_IMERGE> it(tree->merges); + while ((imerge= it++)) + { + double imerge_cost= 0; + ha_rows imerge_total_records= 0; + double tree_read_time; + ha_rows tree_records; + imerge->best_keys= + (SEL_ARG***)alloc_root(&alloc, + (imerge->trees_next - imerge->trees)* + sizeof(void*)); + for (SEL_TREE **ptree= imerge->trees; + ptree != imerge->trees_next; + ptree++) + { + tree_read_time= read_time; + if (get_quick_select_params(*ptree, param, needed_reg, head, + false, + &tree_read_time, &tree_records, + &(imerge->best_keys[ptree - + imerge->trees]))) + goto imerge_fail; + + imerge_cost += tree_read_time; + imerge_total_records += tree_records; + } + imerge_total_records= min(imerge_total_records, + head->file->records); + imerge_cost += imerge_total_records / TIME_FOR_COMPARE; + if (imerge_cost < min_imerge_cost) + { + min_imerge= imerge; + min_imerge_cost= imerge_cost; + min_imerge_records= imerge_total_records; + } +imerge_fail:; + } + + if (!min_imerge) + goto end_free; + + records= min_imerge_records; + /* ok, got minimal imerge, *min_imerge, with cost min_imerge_cost */ + + if (head->used_keys) + { + /* check if "ALL" +"using index" read would be faster */ + int key_for_use= find_shortest_key(head, head->used_keys); + ha_rows total_table_records= (0 == head->file->records)? 1 : + head->file->records; + uint keys_per_block= (head->file->block_size/2/ + (head->key_info[key_for_use].key_length+ + head->file->ref_length) + 1); + double all_index_scan_read_time= ((double)(total_table_records+ + keys_per_block-1)/ + (double) keys_per_block); + + DBUG_PRINT("info", + ("'all' scan will be using key %d, read time %g", + key_for_use, all_index_scan_read_time)); + if (all_index_scan_read_time < min_imerge_cost) + { + DBUG_PRINT("info", + ("index merge would be slower, " + "will do full 'index' scan")); + goto end_free; + } + } + else + { + /* check if "ALL" would be faster */ + if (read_time < min_imerge_cost) + { + DBUG_PRINT("info", + ("index merge would be slower, " + "will do full table scan")); + goto end_free; + } + } + + if (!(quick= quick_imerge= new QUICK_INDEX_MERGE_SELECT(thd, head))) + goto end_free; + + quick->records= min_imerge_records; + quick->read_time= min_imerge_cost; + + my_pthread_setspecific_ptr(THR_MALLOC, &quick_imerge->alloc); + + QUICK_RANGE_SELECT *new_quick; + for (SEL_TREE **ptree = min_imerge->trees; + ptree != min_imerge->trees_next; + ptree++) + { + SEL_ARG **tree_best_key= + min_imerge->best_keys[ptree - min_imerge->trees]; + if ((new_quick= get_quick_select(¶m, + (uint)(tree_best_key- + (*ptree)->keys), + *tree_best_key, + &quick_imerge->alloc))) + { + new_quick->records= min_imerge_records; + new_quick->read_time= min_imerge_cost; + /* + QUICK_RANGE_SELECT::QUICK_RANGE_SELECT leaves THR_MALLOC + pointing to its allocator, restore it back + */ + quick_imerge->last_quick_select= new_quick; + + if (quick_imerge->push_quick_back(new_quick)) + { + delete new_quick; + delete quick; + quick= quick_imerge= NULL; + goto end_free; + } + } + else + { + delete quick; + quick= quick_imerge= NULL; + goto end_free; + } + } + + free_root(&alloc,MYF(0)); + my_pthread_setspecific_ptr(THR_MALLOC,old_root); + if (quick->init()) + { + delete quick; + quick= quick_imerge= NULL; + DBUG_PRINT("error", + ("Failed to allocate index merge structures," + "falling back to full scan.")); + } + else + { + /* with 'using filesort' quick->reset() is not called */ + quick->reset(); + } + + goto end; + } } } +end_free: free_root(&alloc,MYF(0)); // Return memory & allocator my_pthread_setspecific_ptr(THR_MALLOC,old_root); +end: thd->no_errors=0; } - DBUG_EXECUTE("info",print_quick(quick,&needed_reg);); + + DBUG_EXECUTE("info", + { + if (quick_imerge) + print_quick_sel_imerge(quick_imerge, &needed_reg); + else + print_quick_sel_range((QUICK_RANGE_SELECT*)quick, &needed_reg); + } + ); + /* Assume that if the user is using 'limit' we will only need to scan limit rows if we are using a key @@ -762,6 +1182,77 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, DBUG_RETURN(records ? test(quick) : -1); } + +/* + Calculate quick select read time, # of records, and best key to use + without constructing QUICK_SELECT +*/ + +static int get_quick_select_params(SEL_TREE *tree, PARAM& param, + key_map& needed_reg, TABLE *head, + bool index_read_can_be_used, + double* read_time, ha_rows* records, + SEL_ARG*** key_to_read) +{ + int idx; + int result = 1; + /* + Note that there may be trees that have type SEL_TREE::KEY but contain + no key reads at all. For example, tree for expression "key1 is not null" + where key1 is defined as "not null". + */ + SEL_ARG **key,**end; + + for (idx= 0,key=tree->keys, end=key+param.keys ; + key != end ; + key++,idx++) + { + ha_rows found_records; + double found_read_time; + if (*key) + { + uint keynr= param.real_keynr[idx]; + if ((*key)->type == SEL_ARG::MAYBE_KEY || + (*key)->maybe_flag) + needed_reg.set_bit(keynr); + + key_map usable_keys = index_read_can_be_used? + (head->used_keys & ((key_map) 1 << keynr)) : 0; + + found_records=check_quick_select(¶m, idx, *key); + if (found_records != HA_POS_ERROR && found_records > 2 && + usable_keys && + (head->file->index_flags(keynr) & HA_KEY_READ_ONLY)) + { + /* + We can resolve this by only reading through this key. + Assume that we will read trough the whole key range + and that all key blocks are half full (normally things are + much better). + */ + uint keys_per_block= (head->file->block_size/2/ + (head->key_info[keynr].key_length+ + head->file->ref_length) + 1); + found_read_time=((double) (found_records+keys_per_block-1)/ + (double) keys_per_block); + } + else + found_read_time= (head->file->read_time(keynr, + param.range_count, + found_records)+ + (double) found_records / TIME_FOR_COMPARE); + if (*read_time > found_read_time && found_records != HA_POS_ERROR) + { + *read_time= found_read_time; + *records= found_records; + *key_to_read= key; + result = 0; + } + } + } + return result; +} + /* make a select tree of all keys in condition */ static SEL_TREE *get_mm_tree(PARAM *param,COND *cond) @@ -948,6 +1439,7 @@ get_mm_parts(PARAM *param, Field *field, Item_func::Functype type, } sel_arg->part=(uchar) key_part->part; tree->keys[key_part->key]=sel_add(tree->keys[key_part->key],sel_arg); + tree->keys_map |= 1 << key_part->key; } } @@ -1220,6 +1712,8 @@ tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) DBUG_RETURN(tree1); } + bool trees_have_key = false; + key_map result_keys= 0; /* Join the trees key per key */ SEL_ARG **key1,**key2,**end; for (key1= tree1->keys,key2= tree2->keys,end=key1+param->keys ; @@ -1228,6 +1722,7 @@ tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) uint flag=0; if (*key1 || *key2) { + trees_have_key = true; if (*key1 && !(*key1)->simple_key()) flag|=CLONE_KEY1_MAYBE; if (*key2 && !(*key2)->simple_key()) @@ -1236,17 +1731,57 @@ tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) if ((*key1)->type == SEL_ARG::IMPOSSIBLE) { tree1->type= SEL_TREE::IMPOSSIBLE; - break; + DBUG_RETURN(tree1); } + result_keys |= 1 << (key1 - tree1->keys); #ifdef EXTRA_DEBUG (*key1)->test_use_count(*key1); #endif } } + tree1->keys_map= result_keys; + /* dispose index_merge if there is a "range" option */ + if (trees_have_key) + { + tree1->merges.empty(); + DBUG_RETURN(tree1); + } + + /* ok, both trees are index_merge trees */ + imerge_list_and_list(&tree1->merges, &tree2->merges); + DBUG_RETURN(tree1); } +/* + Check if two SEL_TREES can be combined into one without using index_merge +*/ + +bool sel_trees_can_be_ored(SEL_TREE *tree1, SEL_TREE *tree2, PARAM* param) +{ + key_map common_keys= tree1->keys_map & tree2->keys_map; + DBUG_ENTER("sel_trees_can_be_ored"); + + if (!common_keys) + DBUG_RETURN(false); + + /* trees have a common key, check if they refer to same key part */ + SEL_ARG **key1,**key2; + for (uint key_no=0; key_no < param->keys; key_no++, common_keys= common_keys >> 1) + { + if (common_keys & 1) + { + key1= tree1->keys + key_no; + key2= tree2->keys + key_no; + if ((*key1)->part == (*key2)->part) + { + DBUG_RETURN(true); + } + } + } + DBUG_RETURN(false); +} static SEL_TREE * tree_or(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) @@ -1263,19 +1798,61 @@ tree_or(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) if (tree2->type == SEL_TREE::MAYBE) DBUG_RETURN(tree2); - /* Join the trees key per key */ - SEL_ARG **key1,**key2,**end; - SEL_TREE *result=0; - for (key1= tree1->keys,key2= tree2->keys,end=key1+param->keys ; - key1 != end ; key1++,key2++) + SEL_TREE *result= 0; + key_map result_keys= 0; + if (sel_trees_can_be_ored(tree1, tree2, param)) { - *key1=key_or(*key1,*key2); - if (*key1) + /* Join the trees key per key */ + SEL_ARG **key1,**key2,**end; + for (key1= tree1->keys,key2= tree2->keys,end= key1+param->keys ; + key1 != end ; key1++,key2++) { - result=tree1; // Added to tree1 + *key1=key_or(*key1,*key2); + if (*key1) + { + result=tree1; // Added to tree1 + result_keys |= 1 << (key1 - tree1->keys); #ifdef EXTRA_DEBUG - (*key1)->test_use_count(*key1); + (*key1)->test_use_count(*key1); #endif + } + } + if (result) + result->keys_map= result_keys; + } + else + { + /* ok, two trees have KEY type but cannot be used without index merge */ + if (tree1->merges.is_empty() && tree2->merges.is_empty()) + { + SEL_IMERGE *merge; + /* both trees are "range" trees, produce new index merge structure */ + if (!(result= new SEL_TREE()) || !(merge= new SEL_IMERGE()) || + (result->merges.push_back(merge)) || + (merge->or_sel_tree(param, tree1)) || + (merge->or_sel_tree(param, tree2))) + result= NULL; + else + result->type= tree1->type; + } + else if (!tree1->merges.is_empty() && !tree2->merges.is_empty()) + { + if (imerge_list_or_list(param, &tree1->merges, &tree2->merges)) + result= new SEL_TREE(SEL_TREE::ALWAYS); + else + result= tree1; + } + else + { + /* one tree is index merge tree and another is range tree */ + if (tree1->merges.is_empty()) + swap(SEL_TREE*, tree1, tree2); + + /* add tree2 to tree1->merges, checking if it collapses to ALWAYS */ + if (imerge_list_or_tree(param, &tree1->merges, tree2)) + result= new SEL_TREE(SEL_TREE::ALWAYS); + else + result= tree1; } } DBUG_RETURN(result); @@ -2268,15 +2845,17 @@ check_quick_keys(PARAM *param,uint idx,SEL_ARG *key_tree, /**************************************************************************** ** change a tree to a structure to be used by quick_select ** This uses it's own malloc tree +** The caller should call QUICK_SELCT::init for returned quick select ****************************************************************************/ - -static QUICK_SELECT * -get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree) +QUICK_RANGE_SELECT * +get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree, + MEM_ROOT *parent_alloc) { - QUICK_SELECT *quick; + QUICK_RANGE_SELECT *quick; DBUG_ENTER("get_quick_select"); - if ((quick=new QUICK_SELECT(param->thd, param->table, - param->real_keynr[idx]))) + if ((quick=new QUICK_RANGE_SELECT(param->thd, param->table, + param->real_keynr[idx],test(parent_alloc), + parent_alloc))) { if (quick->error || get_quick_keys(param,quick,param->key[idx],key_tree,param->min_key,0, @@ -2288,9 +2867,10 @@ get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree) else { quick->key_parts=(KEY_PART*) - memdup_root(&quick->alloc,(char*) param->key[idx], - sizeof(KEY_PART)* - param->table->key_info[param->real_keynr[idx]].key_parts); + memdup_root(parent_alloc? parent_alloc : &quick->alloc, + (char*) param->key[idx], + sizeof(KEY_PART)* + param->table->key_info[param->real_keynr[idx]].key_parts); } } DBUG_RETURN(quick); @@ -2300,9 +2880,8 @@ get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree) /* ** Fix this to get all possible sub_ranges */ - -static bool -get_quick_keys(PARAM *param,QUICK_SELECT *quick,KEY_PART *key, +bool +get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key, SEL_ARG *key_tree,char *min_key,uint min_key_flag, char *max_key, uint max_key_flag) { @@ -2411,7 +2990,7 @@ get_quick_keys(PARAM *param,QUICK_SELECT *quick,KEY_PART *key, Return 1 if there is only one range and this uses the whole primary key */ -bool QUICK_SELECT::unique_key_range() +bool QUICK_RANGE_SELECT::unique_key_range() { if (ranges.elements == 1) { @@ -2448,16 +3027,23 @@ static bool null_part_in_key(KEY_PART *key_part, const char *key, uint length) ** Create a QUICK RANGE based on a key ****************************************************************************/ -QUICK_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, TABLE_REF *ref) +QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, + TABLE_REF *ref) { table->file->index_end(); // Remove old cursor - QUICK_SELECT *quick=new QUICK_SELECT(thd, table, ref->key, 1); + QUICK_RANGE_SELECT *quick=new QUICK_RANGE_SELECT(thd, table, ref->key, 1); KEY *key_info = &table->key_info[ref->key]; KEY_PART *key_part; uint part; if (!quick) return 0; /* no ranges found */ + if (quick->init()) + { + delete quick; + return 0; + } + if (cp_buffer_from_ref(ref)) { if (thd->is_fatal_error) @@ -2495,11 +3081,204 @@ QUICK_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, TABLE_REF *ref) return 0; } +INDEX_MERGE::INDEX_MERGE(THD *thd_arg) : + dont_save(false), thd(thd_arg) +{} + +String *INDEX_MERGE::Item_rowid::val_str(String *str) +{ + str->set_quick((char*)head->file->ref, head->file->ref_length, collation.collation); + return str; +} + + +/* + Initialize index_merge operation. + RETURN + 0 - OK + other - error. +*/ + +int INDEX_MERGE::init(TABLE *table) +{ + DBUG_ENTER("INDEX_MERGE::init"); + + head= table; + if (!(rowid_item= new Item_rowid(table))) + DBUG_RETURN(1); + + tmp_table_param.copy_field= 0; + tmp_table_param.end_write_records= HA_POS_ERROR; + tmp_table_param.group_length= table->file->ref_length; + tmp_table_param.group_parts= 1; + tmp_table_param.group_null_parts= 0; + tmp_table_param.hidden_field_count= 0; + tmp_table_param.field_count= 0; + tmp_table_param.func_count= 1; + tmp_table_param.sum_func_count= 0; + tmp_table_param.quick_group= 1; + + bzero(&order, sizeof(ORDER)); + order.item= (Item**)&rowid_item; + order.asc= 1; + + fields.push_back(rowid_item); + + temp_table= create_tmp_table(thd, + &tmp_table_param, + fields, + &order, + false, + 0, + SELECT_DISTINCT, + HA_POS_ERROR, + (char *)""); + DBUG_RETURN(!temp_table); +} + +/* + Check if record with ROWID record_pos has already been processed and + if not - store the ROWID value. + + RETURN + 0 - record has not been processed yet + 1 - record has already been processed. + -1 - an error occurred and query processing should be terminated. + Error code is stored in INDEX_MERGE::error +*/ + +int INDEX_MERGE::check_record_in() +{ + return (dont_save)? + check_record() : + put_record(); +} + + +/* + Stop remembering records in check(). + (this should be called just before the last key scan) + + RETURN + 0 - OK + 1 - error occurred initializing table index. +*/ + +int INDEX_MERGE::start_last_quick_select() +{ + int result= 0; + if (!temp_table->uniques) + { + dont_save= true; + result= temp_table->file->index_init(0); + } + return result; +} + + +inline int INDEX_MERGE::put_record() +{ + DBUG_ENTER("INDEX_MERGE::put_record"); + + copy_funcs(tmp_table_param.items_to_copy); + + if ((error= temp_table->file->write_row(temp_table->record[0]))) + { + if (error == HA_ERR_FOUND_DUPP_KEY || + error == HA_ERR_FOUND_DUPP_UNIQUE) + DBUG_RETURN(1); + + DBUG_PRINT("info", + ("Error writing row to temp. table: %d, converting to myisam", + error)); + if (create_myisam_from_heap(current_thd, temp_table, &tmp_table_param, + error,1)) + { + DBUG_PRINT("info", ("Table conversion failed, bailing out")); + DBUG_RETURN(-1); + } + } + + DBUG_RETURN(0); +} + +inline int INDEX_MERGE::check_record() +{ + int result= 1; + DBUG_ENTER("INDEX_MERGE::check_record"); + + if ((error= temp_table->file->index_read(temp_table->record[0], + head->file->ref, + head->file->ref_length, + HA_READ_KEY_EXACT))) + { + if (error != HA_ERR_KEY_NOT_FOUND) + result= -1; + else + result= 0; + } + + DBUG_RETURN(result); +} + +INDEX_MERGE::~INDEX_MERGE() +{ + if (temp_table) + { + DBUG_PRINT("info", ("Freeing temp. table")); + free_tmp_table(current_thd, temp_table); + } + /* rowid_item is freed automatically */ + list_node* node; + node= fields.first_node(); + fields.remove(&node); +} + +int QUICK_INDEX_MERGE_SELECT::get_next() +{ + int result; + int put_result; + DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::get_next"); + + do + { + while ((result= cur_quick_select->get_next()) == HA_ERR_END_OF_FILE) + { + cur_quick_select= cur_quick_it++; + if (!cur_quick_select) + break; + + cur_quick_select->init(); + cur_quick_select->reset(); + + if (last_quick_select == cur_quick_select) + { + if ((result= index_merge.start_last_quick_select())) + DBUG_RETURN(result); + } + } + + if (result) + { + /* + table read error (including HA_ERR_END_OF_FILE on last quick select + in index_merge) + */ + DBUG_RETURN(result); + } + + cur_quick_select->file->position(cur_quick_select->record); + put_result= index_merge.check_record_in(); + }while(put_result == 1); /* While record is processed */ + + DBUG_RETURN((put_result != -1) ? result : index_merge.error); +} + /* get next possible record using quick-struct */ -int QUICK_SELECT::get_next() +int QUICK_RANGE_SELECT::get_next() { - DBUG_ENTER("get_next"); + DBUG_ENTER("QUICK_RANGE_SELECT::get_next"); for (;;) { @@ -2586,7 +3365,7 @@ int QUICK_SELECT::get_next() Returns 0 if key <= range->max_key */ -int QUICK_SELECT::cmp_next(QUICK_RANGE *range_arg) +int QUICK_RANGE_SELECT::cmp_next(QUICK_RANGE *range_arg) { if (range_arg->flag & NO_MAX_RANGE) return 0; /* key can't be to large */ @@ -2627,8 +3406,9 @@ int QUICK_SELECT::cmp_next(QUICK_RANGE *range_arg) for now, this seems to work right at least. */ -QUICK_SELECT_DESC::QUICK_SELECT_DESC(QUICK_SELECT *q, uint used_key_parts) - : QUICK_SELECT(*q), rev_it(rev_ranges) +QUICK_SELECT_DESC::QUICK_SELECT_DESC(QUICK_RANGE_SELECT *q, + uint used_key_parts) + : QUICK_RANGE_SELECT(*q), rev_it(rev_ranges) { bool not_read_after_key = file->table_flags() & HA_NOT_READ_AFTER_KEY; QUICK_RANGE *r; @@ -2895,7 +3675,23 @@ print_key(KEY_PART *key_part,const char *key,uint used_length) } } -static void print_quick(QUICK_SELECT *quick,const key_map* needed_reg) +void print_quick_sel_imerge(QUICK_INDEX_MERGE_SELECT *quick, + const key_map *needed_reg) +{ + DBUG_ENTER("print_param"); + if (! _db_on_ || !quick) + DBUG_VOID_RETURN; + + List_iterator_fast<QUICK_RANGE_SELECT> it(quick->quick_selects); + QUICK_RANGE_SELECT* quick_range_sel; + while ((quick_range_sel= it++)) + { + print_quick_sel_range(quick_range_sel, needed_reg); + } + DBUG_VOID_RETURN; +} + +static void print_quick_sel_range(QUICK_RANGE_SELECT *quick,const key_map *needed_reg) { QUICK_RANGE *range; char buf[MAX_KEY/8+1]; diff --git a/sql/opt_range.h b/sql/opt_range.h index 1c209dc70272f1b2c2774739fa019928b61cf0db..73e30ba642a773b5cb64587137463aac1aa09b2c 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -65,41 +65,198 @@ class QUICK_RANGE :public Sql_alloc { } }; +class INDEX_MERGE; -class QUICK_SELECT { +/* + Quick select interface. + This class is parent for all QUICK_*_SELECT and FT_SELECT classes. +*/ + +class QUICK_SELECT_I +{ public: + ha_rows records; /* estimate of # of records to be retrieved */ + double read_time; /* time to perform this retrieval */ + TABLE *head; + + /* + the only index this quick select uses, or MAX_KEY for + QUICK_INDEX_MERGE_SELECT + */ + uint index; + uint max_used_key_length, used_key_parts; + + QUICK_SELECT_I(); + virtual ~QUICK_SELECT_I(){}; + virtual int init() = 0; + virtual void reset(void) = 0; + virtual int get_next() = 0; /* get next record to retrieve */ + virtual bool reverse_sorted() = 0; + virtual bool unique_key_range() { return false; } + + enum { + QS_TYPE_RANGE = 0, + QS_TYPE_INDEX_MERGE = 1, + QS_TYPE_RANGE_DESC = 2, + QS_TYPE_FULLTEXT = 3 + }; + + /* Get type of this quick select - one of the QS_* values */ + virtual int get_type() = 0; +}; + +struct st_qsel_param; +class SEL_ARG; + +class QUICK_RANGE_SELECT : public QUICK_SELECT_I +{ +protected: bool next,dont_free; +public: int error; - uint index, max_used_key_length, used_key_parts; - TABLE *head; handler *file; byte *record; +protected: + friend void print_quick_sel_range(QUICK_RANGE_SELECT *quick, + key_map needed_reg); + friend QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, + struct st_table_ref *ref); + friend bool get_quick_keys(struct st_qsel_param *param, + QUICK_RANGE_SELECT *quick,KEY_PART *key, + SEL_ARG *key_tree,char *min_key,uint min_key_flag, + char *max_key, uint max_key_flag); + friend QUICK_RANGE_SELECT *get_quick_select(struct st_qsel_param*,uint idx, + SEL_ARG *key_tree, + MEM_ROOT *alloc); + friend class QUICK_SELECT_DESC; + List<QUICK_RANGE> ranges; List_iterator<QUICK_RANGE> it; QUICK_RANGE *range; MEM_ROOT alloc; - - KEY_PART *key_parts; - ha_rows records; - double read_time; - - QUICK_SELECT(THD *thd, TABLE *table,uint index_arg,bool no_alloc=0); - virtual ~QUICK_SELECT(); - void reset(void) { next=0; it.rewind(); } - int init() { return error=file->index_init(index); } - virtual int get_next(); - virtual bool reverse_sorted() { return 0; } + KEY_PART *key_parts; int cmp_next(QUICK_RANGE *range); +public: + QUICK_RANGE_SELECT(THD *thd, TABLE *table,uint index_arg,bool no_alloc=0, + MEM_ROOT *parent_alloc=NULL); + ~QUICK_RANGE_SELECT(); + + void reset(void) { next=0; it.rewind(); } + int init(); + int get_next(); + bool reverse_sorted() { return 0; } bool unique_key_range(); + int get_type() { return QS_TYPE_RANGE; } +}; + +/* + Helper class for keeping track of rows that have been passed to output + in index_merge access method. + + NOTES + Current implementation uses a temporary table to store ROWIDs of rows that + have been passed to output. In the future it might be changed to use more + efficient mechanisms, like Unique class. +*/ + +class INDEX_MERGE +{ +public: + INDEX_MERGE(THD *thd_arg); + ~INDEX_MERGE(); + + int init(TABLE *table); + int check_record_in(); + int start_last_quick_select(); + int error; +private: + /* The only field in temporary table */ + class Item_rowid : public Item_str_func + { + TABLE *head; /* source table */ + public: + Item_rowid(TABLE *table) : head(table) + { + max_length= table->file->ref_length; + collation.set(&my_charset_bin); + }; + const char *func_name() const { return "rowid"; } + bool const_item() const { return 0; } + String *val_str(String *); + void fix_length_and_dec() + {} + }; + + /* Check if record has been processed and save it if it wasn't */ + inline int put_record(); + + /* Check if record has been processed without saving it */ + inline int check_record(); + + /* If true, check_record_in does't store ROWIDs it is passed. */ + bool dont_save; + + THD *thd; + TABLE *head; /* source table */ + TABLE *temp_table; /* temp. table used for values storage */ + TMP_TABLE_PARAM tmp_table_param; /* temp. table creation parameters */ + Item_rowid *rowid_item; /* the only field in temp. table */ + List<Item> fields; /* temp. table fields list + (the only element is rowid_item) */ + ORDER order; /* key for temp. table (rowid_item) */ }; -class QUICK_SELECT_DESC: public QUICK_SELECT +/* + Index merge quick select. + It is implemented as a container for several QUICK_RANGE_SELECTs. +*/ + +class QUICK_INDEX_MERGE_SELECT : public QUICK_SELECT_I +{ +public: + QUICK_INDEX_MERGE_SELECT(THD *thd, TABLE *table); + ~QUICK_INDEX_MERGE_SELECT(); + + int init(); + void reset(void); + int get_next(); + bool reverse_sorted() { return false; } + bool unique_key_range() { return false; } + int get_type() { return QS_TYPE_INDEX_MERGE; } + + bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range); + + /* range quick selects this index_merge read consists of */ + List<QUICK_RANGE_SELECT> quick_selects; + + /* quick select which is currently used for rows retrieval */ + List_iterator_fast<QUICK_RANGE_SELECT> cur_quick_it; + QUICK_RANGE_SELECT* cur_quick_select; + + /* + Last element in quick_selects list. + INDEX_MERGE::start_last_quick_select is called before retrieving + rows for it. + */ + QUICK_RANGE_SELECT* last_quick_select; + + /* + Used to keep track of what records have been already passed to output + when doing index_merge access (NULL means no index_merge) + */ + INDEX_MERGE index_merge; + + MEM_ROOT alloc; +}; + +class QUICK_SELECT_DESC: public QUICK_RANGE_SELECT { public: - QUICK_SELECT_DESC(QUICK_SELECT *q, uint used_key_parts); + QUICK_SELECT_DESC(QUICK_RANGE_SELECT *q, uint used_key_parts); int get_next(); bool reverse_sorted() { return 1; } + int get_type() { return QS_TYPE_RANGE_DESC; } private: int cmp_prev(QUICK_RANGE *range); bool range_reads_after_key(QUICK_RANGE *range); @@ -114,7 +271,7 @@ class QUICK_SELECT_DESC: public QUICK_SELECT class SQL_SELECT :public Sql_alloc { public: - QUICK_SELECT *quick; // If quick-select used + QUICK_SELECT_I *quick; // If quick-select used COND *cond; // where condition TABLE *head; IO_CACHE file; // Positions to used records @@ -134,7 +291,7 @@ class SQL_SELECT :public Sql_alloc { ha_rows limit, bool force_quick_range=0); }; -QUICK_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, +QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, struct st_table_ref *ref); #endif diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 919cf8b969b887f366aade7a350e313c4cc67e64..c0d2c85a7a971ae24d1ed61cf8ad2f303d9b716d 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -575,8 +575,8 @@ int THD::send_explain_fields(select_result *result) item->maybe_null=1; field_list.push_back(item=new Item_empty_string("key",NAME_LEN)); item->maybe_null=1; - field_list.push_back(item=new Item_return_int("key_len",3, - MYSQL_TYPE_LONGLONG)); + field_list.push_back(item=new Item_empty_string("key_len", + NAME_LEN*MAX_KEY)); item->maybe_null=1; field_list.push_back(item=new Item_empty_string("ref", NAME_LEN*MAX_REF_PARTS)); diff --git a/sql/sql_list.h b/sql/sql_list.h index 7200046e6c54c5ba697192c2c88ffdd97e28cbbc..2f0425f1ddc7e690acef5428085dd7c770ecc2cf 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -135,6 +135,12 @@ class base_list :public Sql_alloc last= &first; return tmp->info; } + inline void concat(base_list *list) + { + *last= list->first; + last= list->last; + elements+= list->elements; + } inline list_node* last_node() { return *last; } inline list_node* first_node() { return first;} inline void *head() { return first->info; } @@ -255,6 +261,7 @@ template <class T> class List :public base_list } empty(); } + inline void concat(List<T> *list) { base_list::concat(list); } }; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index eea5ed72a3a213f180f20ac16109e6fde593dde0..662b288c7c2bd901476d608606b247e52e35b84f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -32,7 +32,8 @@ const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref", "MAYBE_REF","ALL","range","index","fulltext", - "ref_or_null","unique_subquery","index_subquery" + "ref_or_null","unique_subquery","index_subquery", + "index_merge" }; const key_map key_map_empty(0); @@ -116,7 +117,7 @@ static int join_read_next_same_or_null(READ_RECORD *info); static COND *make_cond_for_table(COND *cond,table_map table, table_map used_table); static Item* part_of_refkey(TABLE *form,Field *field); -static uint find_shortest_key(TABLE *table, const key_map *usable_keys); +uint find_shortest_key(TABLE *table, const key_map *usable_keys); static bool test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order, ha_rows select_limit, bool no_changes); static int create_sort_index(THD *thd, JOIN *join, ORDER *order, @@ -3417,7 +3418,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) with key reading */ if (tab->needed_reg.is_clear_all() && tab->type != JT_EQ_REF && tab->type != JT_FT && (tab->type != JT_REF || - (uint) tab->ref.key == tab->quick->index)) + (uint) tab->ref.key == tab->quick->index)) { sel->quick=tab->quick; // Use value from get_quick_... sel->quick_keys.clear_all(); @@ -6745,7 +6746,7 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, return reverse; } -static uint find_shortest_key(TABLE *table, const key_map *usable_keys) +uint find_shortest_key(TABLE *table, const key_map *usable_keys) { uint min_length= (uint) ~0; uint best= MAX_KEY; @@ -6879,6 +6880,9 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, } else if (select && select->quick) // Range found by opt_range { + /* assume results are not ordered when index merge is used */ + if (select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + DBUG_RETURN(0); ref_key= select->quick->index; ref_key_parts= select->quick->used_key_parts; } @@ -6913,6 +6917,10 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, } else { + /* + We have verified above that select->quick is not + index_merge quick select. + */ select->quick->index= new_ref_key; select->quick->init(); } @@ -6934,10 +6942,13 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, */ if (!select->quick->reverse_sorted()) { - if (table->file->index_flags(ref_key) & HA_NOT_READ_PREFIX_LAST) + if (table->file->index_flags(ref_key) & HA_NOT_READ_PREFIX_LAST || + (select->quick->get_type() == + QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)) DBUG_RETURN(0); // Use filesort - // ORDER BY range_key DESC - QUICK_SELECT_DESC *tmp=new QUICK_SELECT_DESC(select->quick, + + // ORDER BY range_key DESC + QUICK_SELECT_DESC *tmp=new QUICK_SELECT_DESC((QUICK_RANGE_SELECT*)(select->quick), used_key_parts); if (!tmp || tmp->error) { @@ -7079,8 +7090,11 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, { select->quick=tab->quick; tab->quick=0; - /* We can only use 'Only index' if quick key is same as ref_key */ - if (table->key_read && (uint) tab->ref.key != select->quick->index) + /* + We can only use 'Only index' if quick key is same as ref_key + and in index_merge 'Only index' cannot be used + */ + if (table->key_read && ((uint) tab->ref.key != select->quick->index)) { table->key_read=0; table->file->extra(HA_EXTRA_NO_KEYREAD); @@ -8880,12 +8894,15 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, JOIN_TAB *tab=join->join_tab+i; TABLE *table=tab->table; char buff[512],*buff_ptr=buff; - char buff1[512], buff2[512]; + char buff1[512], buff2[512], buff3[512]; + char keylen_str_buf[64]; char derived_name[64]; String tmp1(buff1,sizeof(buff1),cs); String tmp2(buff2,sizeof(buff2),cs); + String tmp3(buff3,sizeof(buff3),cs); tmp1.length(0); tmp2.length(0); + tmp3.length(0); item_list.empty(); item_list.push_back(new Item_int((int32) @@ -8894,7 +8911,13 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, strlen(join->select_lex->type), cs)); if (tab->type == JT_ALL && tab->select && tab->select->quick) - tab->type= JT_RANGE; + { + if (tab->select->quick->get_type() == + QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + tab->type = JT_INDEX_MERGE; + else + tab->type = JT_RANGE; + } if (table->derived_select_number) { /* Derived table name generation */ @@ -8930,10 +8953,14 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, if (tab->ref.key_parts) { KEY *key_info=table->key_info+ tab->ref.key; + register uint length; item_list.push_back(new Item_string(key_info->name, strlen(key_info->name), system_charset_info)); - item_list.push_back(new Item_int((int32) tab->ref.key_length)); + length= longlong2str(tab->ref.key_length, keylen_str_buf, 10) - + keylen_str_buf; + item_list.push_back(new Item_string(keylen_str_buf, length, + system_charset_info)); for (store_key **ref=tab->ref.key_copy ; *ref ; ref++) { if (tmp2.length()) @@ -8945,18 +8972,60 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, else if (tab->type == JT_NEXT) { KEY *key_info=table->key_info+ tab->index; + register uint length; item_list.push_back(new Item_string(key_info->name, strlen(key_info->name),cs)); - item_list.push_back(new Item_int((int32) key_info->key_length)); + length= longlong2str(key_info->key_length, keylen_str_buf, 10) - + keylen_str_buf; + item_list.push_back(new Item_string(keylen_str_buf, + length, + system_charset_info)); item_list.push_back(item_null); } else if (tab->select && tab->select->quick) { - KEY *key_info=table->key_info+ tab->select->quick->index; - item_list.push_back(new Item_string(key_info->name, - strlen(key_info->name),cs)); - item_list.push_back(new Item_int((int32) tab->select->quick-> - max_used_key_length)); + if (tab->select->quick->get_type() == + QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + { + QUICK_INDEX_MERGE_SELECT *quick_imerge= + (QUICK_INDEX_MERGE_SELECT*)tab->select->quick; + QUICK_RANGE_SELECT *quick; + + List_iterator_fast<QUICK_RANGE_SELECT> it(quick_imerge-> + quick_selects); + while ((quick= it++)) + { + KEY *key_info= table->key_info + quick->index; + register uint length; + if (tmp3.length()) + tmp3.append(','); + + tmp3.append(key_info->name); + + if (tmp2.length()) + tmp2.append(','); + + length= longlong2str(quick->max_used_key_length, keylen_str_buf, + 10) - + keylen_str_buf; + + tmp2.append(keylen_str_buf, length); + } + } + else + { + KEY *key_info= table->key_info + tab->select->quick->index; + register uint length; + tmp3.append(key_info->name); + + length= longlong2str(tab->select->quick->max_used_key_length, + keylen_str_buf, 10) - + keylen_str_buf; + tmp2.append(keylen_str_buf, length); + } + + item_list.push_back(new Item_string(tmp3.ptr(),tmp3.length(),cs)); + item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs)); item_list.push_back(item_null); } else diff --git a/sql/sql_select.h b/sql/sql_select.h index 24854713a0eb4e96922659ca7e7dc39f73330b4e..835ea037777e1c87e8efaadaccf1ffc9bf759ac2 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -76,7 +76,7 @@ typedef struct st_join_cache { enum join_type { JT_UNKNOWN,JT_SYSTEM,JT_CONST,JT_EQ_REF,JT_REF,JT_MAYBE_REF, JT_ALL, JT_RANGE, JT_NEXT, JT_FT, JT_REF_OR_NULL, - JT_UNIQUE_SUBQUERY, JT_INDEX_SUBQUERY}; + JT_UNIQUE_SUBQUERY, JT_INDEX_SUBQUERY, JT_INDEX_MERGE}; class JOIN; @@ -85,7 +85,7 @@ typedef struct st_join_table { KEYUSE *keyuse; /* pointer to first used key */ SQL_SELECT *select; COND *select_cond; - QUICK_SELECT *quick; + QUICK_SELECT_I *quick; Item *on_expr; const char *info; byte *null_ref_key; @@ -311,6 +311,7 @@ void copy_fields(TMP_TABLE_PARAM *param); void copy_funcs(Item **func_ptr); bool create_myisam_from_heap(THD *thd, TABLE *table, TMP_TABLE_PARAM *param, int error, bool ignore_last_dupp_error); +uint find_shortest_key(TABLE *table, key_map usable_keys); /* functions from opt_sum.cc */ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds); diff --git a/sql/sql_test.cc b/sql/sql_test.cc index 449c43c24d15a6fbc128bdc967ae6691cf3b45c9..bd527a4154aaaa9cd3dc674dcb79eb6f62a7373e 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -181,9 +181,39 @@ TEST_join(JOIN *join) " quick select checked for each record (keys: %s)\n", tab->select->quick_keys.print(buf)); else if (tab->select->quick) - fprintf(DBUG_FILE," quick select used on key %s, length: %d\n", + { + int quick_type= tab->select->quick->get_type(); + if ((quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) || + (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC)) + { + fprintf(DBUG_FILE, + " quick select used on key %s, length: %d\n", form->key_info[tab->select->quick->index].name, tab->select->quick->max_used_key_length); + } + else if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + { + QUICK_INDEX_MERGE_SELECT *quick_imerge= + (QUICK_INDEX_MERGE_SELECT*)tab->select->quick; + QUICK_RANGE_SELECT *quick; + fprintf(DBUG_FILE, + " index_merge quick select used\n"); + + List_iterator_fast<QUICK_RANGE_SELECT> it(quick_imerge->quick_selects); + while ((quick = it++)) + { + fprintf(DBUG_FILE, + " range quick select: key %s, length: %d\n", + form->key_info[quick->index].name, + quick->max_used_key_length); + } + } + else + { + fprintf(DBUG_FILE, + " quick select of unknown nature used\n"); + } + } else VOID(fputs(" select used\n",DBUG_FILE)); } diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 4234eba8a14657c841173657e7cd35da50427539..8900d48ee4f7aa26e692ae41516fb7d4953fc624 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -117,7 +117,7 @@ int st_select_lex_unit::prepare(THD *thd, select_result *sel_result, { SELECT_LEX *lex_select_save= thd->lex->current_select; SELECT_LEX *select_cursor,*sl; - DBUG_ENTER("st_select_lex_unit::prepare"); + DBUG_ENTER("st_select_lex_unit::prepare"); /* result object should be reassigned even if preparing already done for diff --git a/sql/sql_update.cc b/sql/sql_update.cc index b87504ddb71253b03d5110e371e65720acfac10d..a70e784f42a96f11bc2c3e21c0a02b5c0eccfc87 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -177,10 +177,18 @@ int mysql_update(THD *thd, init_ftfuncs(thd, &thd->lex->select_lex, 1); /* Check if we are modifying a key that we are used to search with */ if (select && select->quick) - used_key_is_modified= (!select->quick->unique_key_range() && - check_if_key_used(table, - (used_index=select->quick->index), - fields)); + { + if (select->quick->get_type() != QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + { + used_index= select->quick->index; + used_key_is_modified= (!select->quick->unique_key_range() && + check_if_key_used(table,used_index,fields)); + } + else + { + used_key_is_modified= true; + } + } else if ((used_index=table->file->key_used_on_scan) < MAX_KEY) used_key_is_modified=check_if_key_used(table, used_index, fields); else @@ -698,8 +706,26 @@ static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields) case JT_ALL: /* If range search on index */ if (join_tab->quick) - return !check_if_key_used(table, join_tab->quick->index, - *fields); + { + if (join_tab->quick->get_type() != QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + { + return !check_if_key_used(table,join_tab->quick->index,*fields); + } + else + { + QUICK_INDEX_MERGE_SELECT *qsel_imerge= + (QUICK_INDEX_MERGE_SELECT*)(join_tab->quick); + List_iterator_fast<QUICK_RANGE_SELECT> it(qsel_imerge->quick_selects); + QUICK_RANGE_SELECT *quick; + while ((quick= it++)) + { + if (check_if_key_used(table, quick->index, *fields)) + return 0; + } + return 1; + } + } + /* If scanning in clustered key */ if ((table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) && table->primary_key < MAX_KEY)