diff options
40 files changed, 9061 insertions, 472 deletions
diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 7d83cf71529..ca5f8fd0b4c 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -1402,6 +1402,7 @@ The following specify which files/extra groups are read (specified before remain Prohibit update of a VIEW, which does not contain a key of the underlying table and the query uses a LIMIT clause (usually get from GUI tools) + --use-sort-nest Enable the sort nest --use-stat-tables=name Specifies how to use system statistics tables. One of: NEVER, COMPLEMENTARY, PREFERABLY, @@ -1781,6 +1782,7 @@ transaction-isolation REPEATABLE-READ transaction-prealloc-size 4096 transaction-read-only FALSE updatable-views-with-limit YES +use-sort-nest FALSE use-stat-tables PREFERABLY_FOR_QUERIES userstat FALSE verbose TRUE diff --git a/mysql-test/main/sort_nest.result b/mysql-test/main/sort_nest.result new file mode 100644 index 00000000000..11bfd8ad1db --- /dev/null +++ b/mysql-test/main/sort_nest.result @@ -0,0 +1,2123 @@ +set use_sort_nest=1; +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a, a from t0 where a <5; +CREATE TABLE t2 as SELECT * from t1 where a < 5; +CREATE TABLE t3(a int, b int, c int, key(a)); +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 from seq_1_to_100; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +# +# sort nest on (t2,t1) +# ref(sort-nest.b) access on table t3 +# +EXPLAIN SELECT t1.a, t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a=t2.a AND t2.b=t3.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 Using where; Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 5 Using filesort +1 SIMPLE t3 ref a a 5 sort-nest.b 1 Using index +EXPLAIN FORMAT=JSON SELECT t1.a, t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a=t2.a AND t2.b=t3.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL", + "attached_condition": "t2.a = t1.a" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b desc, `sort-nest`.b desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["sort-nest.b"], + "rows": 1, + "filtered": 100, + "using_index": true + } + } +} +SELECT t1.a, t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a=t2.a AND t2.b=t3.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5; +a b b a +4 4 4 4 +3 3 3 3 +2 2 2 2 +1 1 1 1 +0 0 0 0 +DROP TABLE t0,t1,t2,t3; +CREATE TABLE t1(a int, b int); +INSERT INTO t1 SELECT seq-1, seq-1 from seq_1_to_100; +CREATE TABLE t2(a int, b int); +INSERT INTO t2(a,b) VALUES (1,1), (2,2); +INSERT INTO t2 SELECT seq-1, seq-1 from seq_1_to_100; +CREATE TABLE t3(a int, b int); +INSERT INTO t3 SELECT seq-1, seq-1 from seq_1_to_1000; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +CREATE FUNCTION f1(a int) RETURNS INT +BEGIN +DECLARE b INT DEFAULT 0; +RETURN a + b; +END| +Covering 3 table joins + +# sorting on table t2 +# t2.a > 95 would be attached to table t2 +# t1.b=t2.a would be attached to table t1; +# t3.a= sort-nest.b would be attached to table t3 + +EXPLAIN SELECT * FROM t1,t2,t3 +WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a +ORDER BY t2.b +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using where; Using filesort +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT * FROM t1,t2,t3 +WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a +ORDER BY t2.b +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "read_sorted_file": { + "filesort": { + "sort_key": "t2.b", + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 4.6875, + "attached_condition": "t2.a > 95" + } + } + }, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 4.6875, + "attached_condition": "t1.a = t2.a" + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.a = t1.b" + } + } +} +SELECT * FROM t1,t2,t3 +WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a +ORDER BY t2.b +LIMIT 5; +a b a b a b +96 96 96 96 96 96 +97 97 97 97 97 97 +98 98 98 98 98 98 +99 99 99 99 99 99 +# {t1,t2} part of the nest +# t1.a > 95 would be attached to table t1 +# t1.b=t2.a would be attached to table t2; +# t3.a= sort-nest.b would be attached to table t3 + +ALTER TABLE t2 ADD KEY(a); +EXPLAIN SELECT * FROM t1,t2,t3 +WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a +ORDER BY t2.b +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +1 SIMPLE t2 ref a a 5 test.t1.a 1 +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT * FROM t1,t2,t3 +WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a +ORDER BY t2.b +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 4.6875, + "attached_condition": "t1.a > 95 and t1.a is not null" + }, + "table": { + "table_name": "t2", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["test.t1.a"], + "rows": 1, + "filtered": 100 + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.a = `sort-nest`.b" + } + } +} +SELECT * FROM t1,t2,t3 +WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a +ORDER BY t2.b +LIMIT 5; +a b a b a b +96 96 96 96 96 96 +97 97 97 97 97 97 +98 98 98 98 98 98 +99 99 99 99 99 99 +ALTER TABLE t2 DROP KEY a; + +# {t1,t2} part of the sort nest +# (t2.a < 2 or t1.b > 98) would be attached to table t2 + +EXPLAIN SELECT * FROM t1,t2,t3 +WHERE (t3.a < 2 and t2.a < 2) OR (t1.b > 98 and t3.b > 98) +ORDER BY t1.a, t2.b +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using where; Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT * FROM t1,t2,t3 +WHERE (t3.a < 2 and t2.a < 2) OR (t1.b > 98 and t3.b > 98) +ORDER BY t1.a, t2.b +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "1Kb", + "join_type": "BNL", + "attached_condition": "t2.a < 2 or t1.b > 98" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.a, `sort-nest`.b", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.a < 2 and `sort-nest`.a < 2 or `sort-nest`.b > 98 and t3.b > 98" + } + } +} +SELECT * FROM t1,t2,t3 +WHERE (t3.a < 2 and t2.a < 2) OR (t1.b > 98 and t3.b > 98) +ORDER BY t1.a, t2.b +LIMIT 5; +a b a b a b +0 0 1 1 0 0 +0 0 1 1 1 1 +1 1 1 1 0 0 +1 1 1 1 1 1 +2 2 1 1 0 0 + +# {t1,t2} part of the nest +# t2.a < 2 or f1(t1.b) attached to table t2 +# t1.b=t2.a would be attached to table t2; + +EXPLAIN SELECT * FROM t1,t2,t3 +WHERE (t3.a<2 AND t2.a <2) OR (f1(t1.b) > 98 AND t3.b > 98) +ORDER BY t1.a,t2.b +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT * FROM t1,t2,t3 +WHERE (t3.a<2 AND t2.a <2) OR (f1(t1.b) > 98 AND t3.b > 98) +ORDER BY t1.a,t2.b +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "1Kb", + "join_type": "BNL" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.a, `sort-nest`.b", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.a < 2 and `sort-nest`.a < 2 or f1(`sort-nest`.b) > 98 and t3.b > 98" + } + } +} +SELECT * FROM t1,t2,t3 +WHERE (t3.a<2 AND t2.a <2) OR (f1(t1.b) > 98 AND t3.b > 98) +ORDER BY t1.a,t2.b +LIMIT 5; +a b a b a b +0 0 0 0 0 0 +0 0 0 0 1 1 +0 0 1 1 0 0 +0 0 1 1 1 1 +0 0 1 1 0 0 +# +# Removing constant from the order by clause +# +EXPLAIN SELECT * FROM t1,t2 +WHERE t1.a > 95 AND t1.a=t2.a +ORDER BY t2.a +LIMIT 4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using where +EXPLAIN FORMAT=JSON SELECT * FROM t1,t2 +WHERE t1.a > 95 AND t1.a=t2.a +ORDER BY t2.a +LIMIT 4; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "read_sorted_file": { + "filesort": { + "sort_key": "t2.a", + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 4.6875, + "attached_condition": "t1.a > 95" + } + } + }, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 4.6875, + "attached_condition": "t2.a = t1.a" + } + } +} +SELECT * FROM t1,t2 +WHERE t1.a > 95 AND t1.a=t2.a +ORDER BY t2.a +LIMIT 4; +a b a b +96 96 96 96 +97 97 97 97 +98 98 98 98 +99 99 99 99 +EXPLAIN SELECT * FROM t1,t2 +WHERE t1.a > 95 and t1.a=t2.a +ORDER BY 1+2,t2.a limit 4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using where +EXPLAIN FORMAT=JSON SELECT * FROM t1,t2 +WHERE t1.a > 95 and t1.a=t2.a +ORDER BY 1+2,t2.a limit 4; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "read_sorted_file": { + "filesort": { + "sort_key": "t2.a", + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 4.6875, + "attached_condition": "t1.a > 95" + } + } + }, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 4.6875, + "attached_condition": "t2.a = t1.a" + } + } +} +SELECT * FROM t1,t2 +WHERE t1.a > 95 and t1.a=t2.a +ORDER BY 1+2,t2.a limit 4; +a b a b +96 96 96 96 +97 97 97 97 +98 98 98 98 +99 99 99 99 +# +# Equality propagation, both the queries should use a +# sort nest on {t1,t2} +# +EXPLAIN SELECT t3.b, t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.b=t3.b +ORDER BY t1.b DESC, t2.a DESC limit 3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT t3.b, t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.b=t3.b +ORDER BY t1.b DESC, t2.a DESC limit 3; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "1Kb", + "join_type": "BNL" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b desc, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.b = `sort-nest`.b" + } + } +} +SELECT t3.b, t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.b=t3.b +ORDER BY t1.b DESC, t2.a DESC limit 3; +b a b a +99 99 99 99 +99 98 99 99 +99 97 99 99 +EXPLAIN SELECT t3.b, t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.b=t3.b +ORDER BY t3.b DESC, t2.a DESC +LIMIT 3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT t3.b, t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.b=t3.b +ORDER BY t3.b DESC, t2.a DESC +LIMIT 3; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "1Kb", + "join_type": "BNL" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b desc, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.b = `sort-nest`.b" + } + } +} +SELECT t3.b, t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.b=t3.b +ORDER BY t3.b DESC, t2.a DESC +LIMIT 3; +b a b a +99 99 99 99 +99 98 99 99 +99 97 99 99 +# +# Equality propagation also for arguments of expressions, +# the plan should use a sort nest on {t1,t2} +# +EXPLAIN SELECT t3.b,t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.b=t3.b +ORDER BY t3.b + 1 DESC, t2.a DESC +LIMIT 3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT t3.b,t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.b=t3.b +ORDER BY t3.b + 1 DESC, t2.a DESC +LIMIT 3; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "1Kb", + "join_type": "BNL" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b + 1 desc, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.b = `sort-nest`.b" + } + } +} +SELECT t3.b,t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.b=t3.b +ORDER BY t3.b + 1 DESC, t2.a DESC +LIMIT 3; +b a b a +99 99 99 99 +99 98 99 99 +99 97 99 99 +# +# Rows for the sort-nest should be the cardinality of the join of +# inner tables of the sort-nest +# +# Rows for sort nest would be 9894 here +ALTER TABLE t1 ADD KEY(a); +EXPLAIN SELECT t3.b, t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.a > 5 and t1.b=t3.b +ORDER BY t1.b DESC, t2.a DESC +LIMIT 3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL a NULL NULL NULL 100 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +SELECT t3.b, t2.a, t1.b, t1.a +FROM t1,t2,t3 +WHERE t1.a > 5 and t1.b=t3.b +ORDER BY t1.b DESC, t2.a DESC +LIMIT 3; +b a b a +99 99 99 99 +99 98 99 99 +99 97 99 99 +ALTER TABLE t1 DROP KEY a; +# +# With having clause we can't have a sort-nest +# +EXPLAIN SELECT * FROM t1,t2,t3 +WHERE t1.a=t2.a AND t1.b = t3.a +HAVING t1.a > 95 +ORDER BY t2.b,t1.b +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where; Using join buffer (incremental, BNL join) +EXPLAIN FORMAT=JSON SELECT * FROM t1,t2,t3 +WHERE t1.a=t2.a AND t1.b = t3.a +HAVING t1.a > 95 +ORDER BY t2.b,t1.b +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "having_condition": "t1.a > 95", + "filesort": { + "sort_key": "t2.b, t1.b", + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "1Kb", + "join_type": "BNL", + "attached_condition": "t2.a = t1.a" + }, + "block-nl-join": { + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100 + }, + "buffer_type": "incremental", + "buffer_size": "149Kb", + "join_type": "BNL", + "attached_condition": "t3.a = t1.b" + } + } + } + } +} +SELECT * FROM t1,t2,t3 +WHERE t1.a=t2.a AND t1.b = t3.a +HAVING t1.a > 95 +ORDER BY t2.b,t1.b +LIMIT 5; +a b a b a b +96 96 96 96 96 96 +97 97 97 97 97 97 +98 98 98 98 98 98 +99 99 99 99 99 99 +EXPLAIN SELECT * FROM t1,t2,t3 +WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a +ORDER BY t2.b,t1.b +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 102 Using where; Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT * FROM t1,t2,t3 +WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a +ORDER BY t2.b,t1.b +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 4.6875, + "attached_condition": "t1.a > 95" + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 102, + "filtered": 4.6875 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL", + "attached_condition": "t2.a = t1.a" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b, `sort-nest`.b", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.a = `sort-nest`.b" + } + } +} +SELECT * FROM t1,t2,t3 +WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a +ORDER BY t2.b,t1.b +LIMIT 5; +a b a b a b +96 96 96 96 96 96 +97 97 97 97 97 97 +98 98 98 98 98 98 +99 99 99 99 99 99 +# +# Selectivity estimates taken into account for sort-nest{t1,t2} +# +CREATE INDEX idx1 ON t1(b); +CREATE INDEX idx2 ON t2(a); +CREATE INDEX idx3 ON t3(b); +EXPLAIN SELECT * from t1,t2,t3 +WHERE t1.a=t2.a AND t1.b = t3.a AND t1.b < 5 AND t3.b < 900 +ORDER BY t2.b +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range idx1 idx1 5 NULL 3 Using index condition; Using where +1 SIMPLE t2 ref idx2 idx2 5 test.t1.a 1 +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL idx3 NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT * from t1,t2,t3 +WHERE t1.a=t2.a AND t1.b = t3.a AND t1.b < 5 AND t3.b < 900 +ORDER BY t2.b +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["idx1"], + "key": "idx1", + "key_length": "5", + "used_key_parts": ["b"], + "rows": 3, + "filtered": 100, + "index_condition": "t1.b < 5", + "attached_condition": "t1.a is not null" + }, + "table": { + "table_name": "t2", + "access_type": "ref", + "possible_keys": ["idx2"], + "key": "idx2", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["test.t1.a"], + "rows": 1, + "filtered": 100 + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "possible_keys": ["idx3"], + "rows": 1000, + "filtered": 0.6953, + "attached_condition": "t3.a = `sort-nest`.b and t3.b < 900" + } + } +} +SELECT * from t1,t2,t3 +WHERE t1.a=t2.a AND t1.b = t3.a AND t1.b < 5 AND t3.b < 900 +ORDER BY t2.b +LIMIT 5; +a b a b a b +0 0 0 0 0 0 +1 1 1 1 1 1 +1 1 1 1 1 1 +2 2 2 2 2 2 +2 2 2 2 2 2 +DROP INDEX idx1 ON t1; +DROP INDEX idx2 ON t2; +DROP INDEX idx3 ON t3; +DROP TABLE t1,t2,t3; +DROP FUNCTION f1; +Derived table inside a sort-nest +CREATE TABLE t1 (f1 varchar(1), f2 varchar(1), KEY (f2)); +INSERT INTO t1 VALUES +('r','x'), ('x','x'), ('x','x'), ('r','x'), ('x','x'); +CREATE TABLE t2 (f1 varchar(1), f2 varchar(1)); +INSERT INTO t2 VALUES ('s','x'); +CREATE TABLE t3 (f1 varchar(1), f2 varchar(1), KEY (f2)); +INSERT INTO t3 VALUES +(NULL,'x'), (NULL,'f'), ('t','x'), (NULL,'j'), ('g','x'); +CREATE TABLE t4 (f1 int, f2 varchar(1), KEY (f2,f1)) ; +INSERT INTO t4 VALUES (2,'x'), (1,'x'); +EXPLAIN SELECT t.f1 as f +FROM (SELECT DISTINCT t1.* FROM t1,t2 WHERE t2.f2 = t1.f2) t,t3,t4 +WHERE t4.f2 = t3.f2 AND t4.f2 = t.f1 +ORDER BY f LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t4 index f2 f2 9 NULL 2 Using where; Using index +1 PRIMARY <derived2> ref key1 key1 4 test.t4.f2 2 +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 4 Using filesort +1 PRIMARY t3 ref f2 f2 4 sort-nest.f2 2 Using index +2 DERIVED t2 system NULL NULL NULL NULL 1 Using temporary +2 DERIVED t1 ALL f2 NULL NULL NULL 5 Using where +EXPLAIN FORMAT=JSON SELECT t.f1 as f +FROM (SELECT DISTINCT t1.* FROM t1,t2 WHERE t2.f2 = t1.f2) t,t3,t4 +WHERE t4.f2 = t3.f2 AND t4.f2 = t.f1 +ORDER BY f LIMIT 10; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t4", + "access_type": "index", + "possible_keys": ["f2"], + "key": "f2", + "key_length": "9", + "used_key_parts": ["f2", "f1"], + "rows": 2, + "filtered": 100, + "attached_condition": "t4.f2 is not null", + "using_index": true + }, + "table": { + "table_name": "<derived2>", + "access_type": "ref", + "possible_keys": ["key1"], + "key": "key1", + "key_length": "4", + "used_key_parts": ["f1"], + "ref": ["test.t4.f2"], + "rows": 2, + "filtered": 100, + "materialized": { + "query_block": { + "select_id": 2, + "temporary_table": { + "table": { + "table_name": "t2", + "access_type": "system", + "rows": 1, + "filtered": 100 + }, + "table": { + "table_name": "t1", + "access_type": "ALL", + "possible_keys": ["f2"], + "rows": 5, + "filtered": 100, + "attached_condition": "t1.f2 = 'x'" + } + } + } + } + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.f1", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 4, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ref", + "possible_keys": ["f2"], + "key": "f2", + "key_length": "4", + "used_key_parts": ["f2"], + "ref": ["sort-nest.f2"], + "rows": 2, + "filtered": 100, + "using_index": true + } + } +} +SELECT t.f1 as f +FROM (SELECT DISTINCT t1.* FROM t1,t2 WHERE t2.f2 = t1.f2) t,t3,t4 +WHERE t4.f2 = t3.f2 AND t4.f2 = t.f1 +ORDER BY f LIMIT 10; +f +x +x +x +x +x +x +should use the sort-nest too like the query above +EXPLAIN SELECT t4.f1 as f, t.f1 as g +FROM (SELECT DISTINCT t1.* FROM t1,t2 WHERE t2.f2 = t1.f2) t,t3,t4 +WHERE t4.f2 = t3.f2 AND t4.f2 = t.f1 ORDER BY f,g +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t4 index f2 f2 9 NULL 2 Using where; Using index +1 PRIMARY <derived2> ref key1 key1 4 test.t4.f2 2 +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 4 Using filesort +1 PRIMARY t3 ref f2 f2 4 sort-nest.f2 2 Using index +2 DERIVED t2 system NULL NULL NULL NULL 1 Using temporary +2 DERIVED t1 ALL f2 NULL NULL NULL 5 Using where +EXPLAIN FORMAT=JSON SELECT t4.f1 as f, t.f1 as g +FROM (SELECT DISTINCT t1.* FROM t1,t2 WHERE t2.f2 = t1.f2) t,t3,t4 +WHERE t4.f2 = t3.f2 AND t4.f2 = t.f1 ORDER BY f,g +LIMIT 10; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t4", + "access_type": "index", + "possible_keys": ["f2"], + "key": "f2", + "key_length": "9", + "used_key_parts": ["f2", "f1"], + "rows": 2, + "filtered": 100, + "attached_condition": "t4.f2 is not null", + "using_index": true + }, + "table": { + "table_name": "<derived2>", + "access_type": "ref", + "possible_keys": ["key1"], + "key": "key1", + "key_length": "4", + "used_key_parts": ["f1"], + "ref": ["test.t4.f2"], + "rows": 2, + "filtered": 100, + "materialized": { + "query_block": { + "select_id": 2, + "temporary_table": { + "table": { + "table_name": "t2", + "access_type": "system", + "rows": 1, + "filtered": 100 + }, + "table": { + "table_name": "t1", + "access_type": "ALL", + "possible_keys": ["f2"], + "rows": 5, + "filtered": 100, + "attached_condition": "t1.f2 = 'x'" + } + } + } + } + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.f1, `sort-nest`.f1", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 4, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ref", + "possible_keys": ["f2"], + "key": "f2", + "key_length": "4", + "used_key_parts": ["f2"], + "ref": ["sort-nest.f2"], + "rows": 2, + "filtered": 100, + "using_index": true + } + } +} +SELECT t4.f1 as f, t.f1 as g +FROM (SELECT DISTINCT t1.* FROM t1,t2 WHERE t2.f2 = t1.f2) t,t3,t4 +WHERE t4.f2 = t3.f2 AND t4.f2 = t.f1 ORDER BY f,g +LIMIT 10; +f g +1 x +1 x +1 x +2 x +2 x +2 x +DROP TABLE t1,t2,t3,t4; +views inside a sort-nest +CREATE TABLE t0 (x int); +CREATE TABLE t1 (a int); +CREATE TABLE t2 (b int, c int default 0); +INSERT t0 (x) VALUES (0),(10); +INSERT t1 (a) VALUES (1), (2); +INSERT t2 (b) VALUES (1), (2); +CREATE VIEW v1 as SELECT t2.b,t2.c FROM t1, t2 +WHERE t1.a=t2.b and t2.b < 3 WITH CHECK OPTION; +EXPLAIN SELECT * FROM v1,t0 +WHERE b<3 +ORDER BY x,b DESC +LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using where +1 SIMPLE t0 ALL NULL NULL NULL NULL 2 Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 Using where +EXPLAIN FORMAT=JSON SELECT * FROM v1,t0 +WHERE b<3 +ORDER BY x,b DESC +LIMIT 2; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 2, + "filtered": 100, + "attached_condition": "t1.a < 3 and t1.a < 3" + }, + "block-nl-join": { + "table": { + "table_name": "t0", + "access_type": "ALL", + "rows": 2, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "65", + "join_type": "BNL" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.x, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 2, + "filtered": 100, + "attached_condition": "t2.b = `sort-nest`.a" + } + } +} +SELECT * FROM v1,t0 +WHERE b<3 +ORDER BY x,b DESC +LIMIT 2; +b c x +2 0 0 +1 0 0 +DROP TABLE t0,t1,t2; +DROP VIEW v1; +# Primary key considered as the key that could achieve ordering +CREATE TABLE t1 (id char(32) NOT NULL primary key); +INSERT INTO t1 VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9); +CREATE TABLE t2 (id char(32) NOT NULL primary key); +INSERT INTO t2 VALUES (0), (1), (2), (3); +EXPLAIN SELECT t1.id +FROM t1 INNER JOIN t2 ON t1.id=t2.id +ORDER BY t2.id LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index PRIMARY PRIMARY 32 NULL 2 Using index +1 SIMPLE t1 eq_ref PRIMARY PRIMARY 32 test.t2.id 1 Using index +EXPLAIN FORMAT=JSON SELECT t1.id +FROM t1 INNER JOIN t2 ON t1.id=t2.id +ORDER BY t2.id LIMIT 2; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "index", + "possible_keys": ["PRIMARY"], + "key": "PRIMARY", + "key_length": "32", + "used_key_parts": ["id"], + "rows": 2, + "filtered": 100, + "using_index": true + }, + "table": { + "table_name": "t1", + "access_type": "eq_ref", + "possible_keys": ["PRIMARY"], + "key": "PRIMARY", + "key_length": "32", + "used_key_parts": ["id"], + "ref": ["test.t2.id"], + "rows": 1, + "filtered": 100, + "using_index": true + } + } +} +SELECT t1.id +FROM t1 INNER JOIN t2 ON t1.id=t2.id +ORDER BY t2.id LIMIT 2; +id +0 +1 +DROP TABLE t1,t2; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT seq-1, seq-1 from seq_1_to_10; +CREATE TABLE t2 as SELECT * from t1; +CREATE TABLE t3 (a int, b int); +INSERT INTO t3 SELECT seq-1, seq-1 from seq_1_to_1000; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +# +# Outer Join +# +# sort-nest(t2,t1) and t3 outside the nest +EXPLAIN SELECT * from t2,t1 left join t3 on t3.a=t1.b +order by t2.a desc,t1.a desc limit 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 10 +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT * from t2,t1 left join t3 on t3.a=t1.b +order by t2.a desc,t1.a desc limit 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "const_condition": "1", + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.a desc, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "trigcond(t3.a = `sort-nest`.b)" + } + } +} +SELECT * from t2,t1 left join t3 on t3.a=t1.b +order by t2.a desc,t1.a desc limit 5; +a b a b a b +9 9 9 9 9 9 +9 9 8 8 8 8 +9 9 7 7 7 7 +9 9 6 6 6 6 +9 9 5 5 5 5 +# +# no sort-nest as all the inner tables of the outer join will be +# inside the nest, this should use temporary table to sort after the +# entire join is computed +# +EXPLAIN SELECT * FROM t2 LEFT JOIN (t1 LEFT JOIN t3 ON t3.a=t1.b) +ON t2.b=t1.a +ORDER BY t2.a DESC,t1.a DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 10 Using temporary; Using filesort +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where; Using join buffer (incremental, BNL join) +EXPLAIN FORMAT=JSON SELECT * FROM t2 LEFT JOIN (t1 LEFT JOIN t3 ON t3.a=t1.b) +ON t2.b=t1.a +ORDER BY t2.a DESC,t1.a DESC LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "const_condition": "1", + "filesort": { + "sort_key": "t2.a desc, t1.a desc", + "temporary_table": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "141", + "join_type": "BNL", + "attached_condition": "trigcond(t1.a = t2.b)" + }, + "block-nl-join": { + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100 + }, + "buffer_type": "incremental", + "buffer_size": "1Kb", + "join_type": "BNL", + "attached_condition": "trigcond(t3.a = t1.b)" + } + } + } + } +} +SELECT * FROM t2 LEFT JOIN (t1 LEFT JOIN t3 ON t3.a=t1.b) +ON t2.b=t1.a +ORDER BY t2.a DESC,t1.a DESC LIMIT 5; +a b a b a b +9 9 9 9 9 9 +8 8 8 8 8 8 +7 7 7 7 7 7 +6 6 6 6 6 6 +5 5 5 5 5 5 +DROP TABLE t1,t2,t3; +# +# Sort-nest with prepared statements +# +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT seq-1,seq-1 from seq_1_to_10; +CREATE TABLE t2 as SELECT * from t1; +CREATE TABLE t3 (a int, b int); +INSERT INTO t3 SELECT seq-1, seq-1 from seq_1_to_1000; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +# sort-nest on table t1,t2 +prepare ps1 from "EXPLAIN SELECT * FROM t2,t1,t3 + WHERE t3.a=t1.b+1 + ORDER BY t2.a DESC,t1.a DESC + LIMIT 5"; +EXECUTE ps1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 10 +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +EXECUTE ps1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 10 +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t3 ALL NULL NULL NULL NULL 1000 Using where +# sort-nest on table t1,t2 +PREPARE ps2 from "EXPLAIN FORMAT=JSON + SELECT * from t2,t1,t3 + WHERE t3.a=t1.b+1 + ORDER BY t2.a DESC, t1.a DESC + LIMIT 5"; +EXECUTE ps2; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.a desc, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.a = `sort-nest`.b + 1" + } + } +} +EXECUTE ps2; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.a desc, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100, + "attached_condition": "t3.a = `sort-nest`.b + 1" + } + } +} +# sort-nest on table t1,t2 +PREPARE ps3 from "SELECT * from t2,t1,t3 + WHERE t3.a=t1.b+1 + ORDER BY t2.a DESC, t1.a DESC + LIMIT 5"; +EXECUTE ps3; +a b a b a b +9 9 9 9 10 10 +9 9 8 8 9 9 +9 9 7 7 8 8 +9 9 6 6 7 7 +9 9 5 5 6 6 +EXECUTE ps3; +a b a b a b +9 9 9 9 10 10 +9 9 8 8 9 9 +9 9 7 7 8 8 +9 9 6 6 7 7 +9 9 5 5 6 6 +DEALLOCATE PREPARE ps1; +DEALLOCATE PREPARE ps2; +DEALLOCATE PREPARE ps3; +DROP TABLE t1,t2,t3; +# INDEPENDENT SUBQUERIES +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a,a from t0 where a <5; +CREATE TABLE t2 as SELECT * from t1 where a < 5; +CREATE TABLE t3 (a int, b int, c int); +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 from seq_1_to_1000; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +# +# sort-nest(t2,t1) and independent subquery in the SELECT list +# +EXPLAIN SELECT (SELECT A.a FROM t3 A WHERE A.a > 5 limit 1) as x, +t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a = t2.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 +1 PRIMARY t2 ALL NULL NULL NULL NULL 5 Using where; Using join buffer (flat, BNL join) +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 PRIMARY t3 ALL NULL NULL NULL NULL 1000 +2 SUBQUERY A ALL NULL NULL NULL NULL 1000 Using where +EXPLAIN FORMAT=JSON SELECT (SELECT A.a FROM t3 A WHERE A.a > 5 limit 1) as x, +t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a = t2.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL", + "attached_condition": "t2.a = t1.a" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b desc, `sort-nest`.b desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100 + }, + "subqueries": [ + { + "query_block": { + "select_id": 2, + "table": { + "table_name": "A", + "access_type": "ALL", + "rows": 1000, + "filtered": 99.219, + "attached_condition": "A.a > 5" + } + } + } + ] + } +} +SELECT (SELECT A.a FROM t3 A WHERE A.a > 5 limit 1) as x, +t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a = t2.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5; +x b b a +6 4 4 0 +6 4 4 1 +6 4 4 2 +6 4 4 3 +6 4 4 4 +DROP TABLE t0,t1,t2,t3; +# +# Const table should not form the sort-nest +# +CREATE TABLE t1 (i1 integer NOT NULL PRIMARY KEY); +CREATE TABLE t2 (i2 integer NOT NULL PRIMARY KEY); +CREATE TABLE t3 (i3 integer); +INSERT INTO t1 VALUES (1), (2), (3), (4), (5), (6), (7), (8), +(9), (10), (11), (12); +INSERT INTO t2 SELECT * FROM t1; +EXPLAIN SELECT t1.*, t2.* +FROM t1 JOIN t2 ON t1.i1 = t2.i2 +LEFT JOIN t3 ON t2.i2 = t3.i3 +ORDER BY t1.i1 +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t3 system NULL NULL NULL NULL 0 Const row not found +1 SIMPLE t1 index PRIMARY PRIMARY 4 NULL 12 Using index +1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.i1 1 Using index +SELECT t1.*, t2.* +FROM t1 JOIN t2 ON t1.i1 = t2.i2 +LEFT JOIN t3 ON t2.i2 = t3.i3 +ORDER BY t1.i1 +LIMIT 5; +i1 i2 +1 1 +2 2 +3 3 +4 4 +5 5 +DROP TABLE t1,t2,t3; +# All tables are const tables +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 VALUES (0,0); +CREATE TABLE t2(a int, b int); +INSERT INTO t2 VALUES (0,0); +CREATE TABLE t3(a int, b int); +INSERT INTO t3 VALUES (0,0); +EXPLAIN SELECT t1.a,t2.a,t3.a +FROM t1,t2,t3 +WHERE t1.b = t2.b AND t3.b=t1.b +ORDER BY t2.a DESC,t1.a DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 system NULL NULL NULL NULL 1 +1 SIMPLE t2 system NULL NULL NULL NULL 1 +1 SIMPLE t3 system NULL NULL NULL NULL 1 +SELECT t1.a,t2.a,t3.a +FROM t1,t2,t3 +WHERE t1.b = t2.b AND t3.b=t1.b +ORDER BY t2.a DESC,t1.a DESC +LIMIT 5; +a a a +0 0 0 +DROP TABLE t1,t2,t3; +# +# Tests where Index(scan, ref or range access) satisfies the ORDERING +# +CREATE TABLE t1 (a int, b int, c int, KEY a_b (a,b), KEY a_c (a,c)); +INSERT INTO t1 VALUES (0,1,0), (0,2,0), (0,3,0), (0,4,0), (0,5,0), (0,6,0); +INSERT INTO t1 values (1,7,1), (1,8,1), (1,9,1), (1,10,1), (1,11,1), (1,12,1); +INSERT INTO t1 VALUES (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +INSERT INTO t1 VALUES (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +INSERT INTO t1 VALUES (1,1,2); +# index key a_b, no need for filesort +EXPLAIN SELECT a,b,c FROM t1 +WHERE a=1 and c=2 +ORDER BY b +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a_b,a_c a_b 5 NULL 19 Using index condition; Using where +SELECT a,b,c FROM t1 +WHERE a=1 and c=2 +ORDER BY b +LIMIT 10; +a b c +1 1 2 +1 7 2 +1 7 2 +1 8 2 +1 8 2 +1 9 2 +1 9 2 +1 10 2 +1 10 2 +1 11 2 +DROP TABLE t1; +# +# Testing ORDER BY LIMIT with OFFSET, should show the same plan and same +# estimate of rows for the sort-nest +# +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a, a from t0; +CREATE TABLE t2 as SELECT * from t1; +CREATE TABLE t3(a int, b int, c int, key(a)); +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 from seq_1_to_100; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +EXPLAIN SELECT t1.a, t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a=t2.a AND t3.a = t2.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 +1 SIMPLE t2 ALL NULL NULL NULL NULL 10 Using where; Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 10 Using filesort +1 SIMPLE t3 ref a a 5 sort-nest.a 1 Using index +SELECT t1.a, t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a=t2.a AND t3.a = t2.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 10; +a b b a +9 9 9 9 +8 8 8 8 +7 7 7 7 +6 6 6 6 +5 5 5 5 +4 4 4 4 +3 3 3 3 +2 2 2 2 +1 1 1 1 +0 0 0 0 +EXPLAIN SELECT t1.a, t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a=t2.a AND t3.a=t2.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5 OFFSET 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 +1 SIMPLE t2 ALL NULL NULL NULL NULL 10 Using where; Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 10 Using filesort +1 SIMPLE t3 ref a a 5 sort-nest.a 1 Using index +SELECT t1.a, t2.b, t1.b, t3.a +FROM t1,t2,t3 +WHERE t1.a=t2.a AND t3.a=t2.a +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5 OFFSET 5; +a b b a +4 4 4 4 +3 3 3 3 +2 2 2 2 +1 1 1 1 +0 0 0 0 +drop table t0,t1,t2,t3; +# +# Constant removed from ORDER BY , so no need of sorting +# +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a, a from t0; +CREATE TABLE t2 as SELECT * from t1; +set use_sort_nest=1; +EXPLAIN SELECT * +FROM t1,t2 +WHERE t1.a=t2.a and t1.b= 4 +ORDER BY t1.b DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 10 Using where +SELECT * +FROM t1,t2 +WHERE t1.a=t2.a and t1.b= 4 +ORDER BY t1.b DESC +LIMIT 5; +a b a b +4 4 4 4 +drop table t0,t1,t2; +# +# ORDER BY clause containing expressions +# +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a, a from t0 where a <5; +CREATE TABLE t2 as SELECT * from t1 where a < 5; +CREATE TABLE t3(a int, b int, c int, key(a)); +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 from seq_1_to_10; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +set use_sort_nest=1; +EXPLAIN SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY abs(t3.a+t1.b) DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 Using where; Using join buffer (flat, BNL join) +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 5 Using filesort +1 SIMPLE t3 ref a a 5 sort-nest.a 1 Using index +SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY abs(t3.a+t1.b) DESC +LIMIT 5; +a b b t1.b + t2.b +4 4 4 8 +3 3 3 6 +2 2 2 4 +1 1 1 2 +0 0 0 0 +set use_sort_nest=0; +SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY abs(t3.a+t1.b) DESC +LIMIT 5; +a b b t1.b + t2.b +4 4 4 8 +3 3 3 6 +2 2 2 4 +1 1 1 2 +0 0 0 0 +# +# No sort nest where ORDER BY item are expensive to compute like +# stored functions, subqueries etc +CREATE FUNCTION f1(a INT) RETURNS INT +BEGIN +RETURN a; +END| +set use_sort_nest=1; +EXPLAIN SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY f1(t3.a+t1.b) DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t3 ref a a 5 test.t2.a 1 Using index +EXPLAIN FORMAT=JSON SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY f1(t3.a+t1.b) DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "f1(t3.a + t1.b) desc", + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL", + "attached_condition": "t2.b = t1.a and t2.a is not null" + }, + "table": { + "table_name": "t3", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["test.t2.a"], + "rows": 1, + "filtered": 100, + "using_index": true + } + } + } + } +} +SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY f1(t3.a+t1.b) DESC +LIMIT 5; +a b b t1.b + t2.b +4 4 4 8 +3 3 3 6 +2 2 2 4 +1 1 1 2 +0 0 0 0 +set use_sort_nest=0; +EXPLAIN SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY f1(t3.a+t1.b) DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t3 ref a a 5 test.t2.a 1 Using index +SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY f1(t3.a+t1.b) DESC +LIMIT 5; +a b b t1.b + t2.b +4 4 4 8 +3 3 3 6 +2 2 2 4 +1 1 1 2 +0 0 0 0 +drop function f1; +# +# Window function in order by clause, sort-nest not allowed +# +set use_sort_nest=1; +EXPLAIN SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b AND t2.a=t3.a +ORDER BY row_number() OVER (ORDER BY t1.a) DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t3 ref a a 5 test.t2.a 1 Using index +EXPLAIN FORMAT=JSON SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b AND t2.a=t3.a +ORDER BY row_number() OVER (ORDER BY t1.a) DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "row_number() over ( order by t1.a) desc", + "window_functions_computation": { + "sorts": { + "filesort": { + "sort_key": "t1.a" + } + }, + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL", + "attached_condition": "t2.b = t1.a and t2.a is not null" + }, + "table": { + "table_name": "t3", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["test.t2.a"], + "rows": 1, + "filtered": 100, + "using_index": true + } + } + } + } + } +} +SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b AND t2.a=t3.a +ORDER BY row_number() OVER (ORDER BY t1.a) DESC +LIMIT 5; +a b b t1.b + t2.b +4 4 4 8 +3 3 3 6 +2 2 2 4 +1 1 1 2 +0 0 0 0 +set use_sort_nest=0; +SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b AND t2.a=t3.a +ORDER BY row_number() OVER (ORDER BY t1.a) DESC +LIMIT 5; +a b b t1.b + t2.b +4 4 4 8 +3 3 3 6 +2 2 2 4 +1 1 1 2 +0 0 0 0 +# +# Subqueries used in order by clause +# +set use_sort_nest=1; +EXPLAIN SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY (select A.a+t1.b from t1 A limit 1) +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 Using temporary; Using filesort +1 PRIMARY t2 ALL NULL NULL NULL NULL 5 Using where; Using join buffer (flat, BNL join) +1 PRIMARY t3 ref a a 5 test.t2.a 1 Using index +2 DEPENDENT SUBQUERY A ALL NULL NULL NULL NULL 5 +EXPLAIN FORMAT=JSON SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY (select A.a+t1.b from t1 A limit 1) +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "filesort": { + "sort_key": "(subquery#2)", + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL", + "attached_condition": "t2.b = t1.a and t2.a is not null" + }, + "table": { + "table_name": "t3", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["test.t2.a"], + "rows": 1, + "filtered": 100, + "using_index": true + }, + "subqueries": [ + { + "expression_cache": { + "state": "uninitialized", + "query_block": { + "select_id": 2, + "table": { + "table_name": "A", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + } + } + } + } + ] + } + } + } +} +SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY (select A.a+t1.b from t1 A limit 1) +LIMIT 5; +a b b t1.b + t2.b +0 0 0 0 +1 1 1 2 +2 2 2 4 +3 3 3 6 +4 4 4 8 +set use_sort_nest=0; +SELECT t1.a, t2.b, t1.b, t1.b + t2.b +FROM t1,t2, t3 +WHERE t1.a=t2.b and t2.a=t3.a +ORDER BY (select A.a+t1.b from t1 A limit 1) +LIMIT 5; +a b b t1.b + t2.b +0 0 0 0 +1 1 1 2 +2 2 2 4 +3 3 3 6 +4 4 4 8 +drop table t0,t1,t2,t3; diff --git a/mysql-test/main/sort_nest.test b/mysql-test/main/sort_nest.test new file mode 100644 index 00000000000..a26dba836fe --- /dev/null +++ b/mysql-test/main/sort_nest.test @@ -0,0 +1,670 @@ +--source include/have_sequence.inc + +set use_sort_nest=1; + +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a, a from t0 where a <5; +CREATE TABLE t2 as SELECT * from t1 where a < 5; +CREATE TABLE t3(a int, b int, c int, key(a)); +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 from seq_1_to_100; + +--disable_result_log +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +--enable_result_log + +--echo # +--echo # sort nest on (t2,t1) +--echo # ref(sort-nest.b) access on table t3 +--echo # + +let $query= SELECT t1.a, t2.b, t1.b, t3.a + FROM t1,t2,t3 + WHERE t1.a=t2.a AND t2.b=t3.a + ORDER BY t2.b DESC, t1.b DESC + LIMIT 5; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +DROP TABLE t0,t1,t2,t3; + +CREATE TABLE t1(a int, b int); +INSERT INTO t1 SELECT seq-1, seq-1 from seq_1_to_100; + +CREATE TABLE t2(a int, b int); +INSERT INTO t2(a,b) VALUES (1,1), (2,2); +INSERT INTO t2 SELECT seq-1, seq-1 from seq_1_to_100; +CREATE TABLE t3(a int, b int); +INSERT INTO t3 SELECT seq-1, seq-1 from seq_1_to_1000; + +--disable_result_log +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +--enable_result_log + +delimiter |; +CREATE FUNCTION f1(a int) RETURNS INT +BEGIN + DECLARE b INT DEFAULT 0; + RETURN a + b; +END| +delimiter ;| + +--echo Covering 3 table joins + +--echo +--echo # sorting on table t2 +--echo # t2.a > 95 would be attached to table t2 +--echo # t1.b=t2.a would be attached to table t1; +--echo # t3.a= sort-nest.b would be attached to table t3 +--echo + +let $query= SELECT * FROM t1,t2,t3 + WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a + ORDER BY t2.b + LIMIT 5; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo # {t1,t2} part of the nest +--echo # t1.a > 95 would be attached to table t1 +--echo # t1.b=t2.a would be attached to table t2; +--echo # t3.a= sort-nest.b would be attached to table t3 +--echo + +let $query= SELECT * FROM t1,t2,t3 + WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a + ORDER BY t2.b + LIMIT 5; + +ALTER TABLE t2 ADD KEY(a); +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +ALTER TABLE t2 DROP KEY a; + +--echo +--echo # {t1,t2} part of the sort nest +--echo # (t2.a < 2 or t1.b > 98) would be attached to table t2 +--echo + +let $query= SELECT * FROM t1,t2,t3 + WHERE (t3.a < 2 and t2.a < 2) OR (t1.b > 98 and t3.b > 98) + ORDER BY t1.a, t2.b + LIMIT 5; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo +--echo # {t1,t2} part of the nest +--echo # t2.a < 2 or f1(t1.b) attached to table t2 +--echo # t1.b=t2.a would be attached to table t2; +--echo + +let $query= SELECT * FROM t1,t2,t3 + WHERE (t3.a<2 AND t2.a <2) OR (f1(t1.b) > 98 AND t3.b > 98) + ORDER BY t1.a,t2.b + LIMIT 5; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo # +--echo # Removing constant from the order by clause +--echo # + +let $query= SELECT * FROM t1,t2 + WHERE t1.a > 95 AND t1.a=t2.a + ORDER BY t2.a + LIMIT 4; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +let $query= SELECT * FROM t1,t2 + WHERE t1.a > 95 and t1.a=t2.a + ORDER BY 1+2,t2.a limit 4; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo # +--echo # Equality propagation, both the queries should use a +--echo # sort nest on {t1,t2} +--echo # + +let $query= SELECT t3.b, t2.a, t1.b, t1.a + FROM t1,t2,t3 + WHERE t1.b=t3.b + ORDER BY t1.b DESC, t2.a DESC limit 3; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +let $query= SELECT t3.b, t2.a, t1.b, t1.a + FROM t1,t2,t3 + WHERE t1.b=t3.b + ORDER BY t3.b DESC, t2.a DESC + LIMIT 3; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo # +--echo # Equality propagation also for arguments of expressions, +--echo # the plan should use a sort nest on {t1,t2} +--echo # + +let $query=SELECT t3.b,t2.a, t1.b, t1.a + FROM t1,t2,t3 + WHERE t1.b=t3.b + ORDER BY t3.b + 1 DESC, t2.a DESC + LIMIT 3; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo # +--echo # Rows for the sort-nest should be the cardinality of the join of +--echo # inner tables of the sort-nest +--echo # + +--echo # Rows for sort nest would be 9894 here + +ALTER TABLE t1 ADD KEY(a); +let $query= SELECT t3.b, t2.a, t1.b, t1.a + FROM t1,t2,t3 + WHERE t1.a > 5 and t1.b=t3.b + ORDER BY t1.b DESC, t2.a DESC + LIMIT 3; + +eval EXPLAIN $query; +eval $query; +ALTER TABLE t1 DROP KEY a; + +--echo # +--echo # With having clause we can't have a sort-nest +--echo # + +let $query= SELECT * FROM t1,t2,t3 + WHERE t1.a=t2.a AND t1.b = t3.a + HAVING t1.a > 95 + ORDER BY t2.b,t1.b + LIMIT 5; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +let $query= SELECT * FROM t1,t2,t3 + WHERE t1.a > 95 AND t1.a=t2.a AND t1.b = t3.a + ORDER BY t2.b,t1.b + LIMIT 5; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo # +--echo # Selectivity estimates taken into account for sort-nest{t1,t2} +--echo # + +CREATE INDEX idx1 ON t1(b); +CREATE INDEX idx2 ON t2(a); +CREATE INDEX idx3 ON t3(b); + +let $query= SELECT * from t1,t2,t3 + WHERE t1.a=t2.a AND t1.b = t3.a AND t1.b < 5 AND t3.b < 900 + ORDER BY t2.b + LIMIT 5; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +DROP INDEX idx1 ON t1; +DROP INDEX idx2 ON t2; +DROP INDEX idx3 ON t3; +DROP TABLE t1,t2,t3; +DROP FUNCTION f1; + +--echo Derived table inside a sort-nest + +CREATE TABLE t1 (f1 varchar(1), f2 varchar(1), KEY (f2)); +INSERT INTO t1 VALUES +('r','x'), ('x','x'), ('x','x'), ('r','x'), ('x','x'); + +CREATE TABLE t2 (f1 varchar(1), f2 varchar(1)); +INSERT INTO t2 VALUES ('s','x'); + +CREATE TABLE t3 (f1 varchar(1), f2 varchar(1), KEY (f2)); +INSERT INTO t3 VALUES +(NULL,'x'), (NULL,'f'), ('t','x'), (NULL,'j'), ('g','x'); + +CREATE TABLE t4 (f1 int, f2 varchar(1), KEY (f2,f1)) ; +INSERT INTO t4 VALUES (2,'x'), (1,'x'); + +let $query= SELECT t.f1 as f + FROM (SELECT DISTINCT t1.* FROM t1,t2 WHERE t2.f2 = t1.f2) t,t3,t4 + WHERE t4.f2 = t3.f2 AND t4.f2 = t.f1 + ORDER BY f LIMIT 10; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo should use the sort-nest too like the query above + +let $query= SELECT t4.f1 as f, t.f1 as g + FROM (SELECT DISTINCT t1.* FROM t1,t2 WHERE t2.f2 = t1.f2) t,t3,t4 + WHERE t4.f2 = t3.f2 AND t4.f2 = t.f1 ORDER BY f,g + LIMIT 10; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +DROP TABLE t1,t2,t3,t4; + +--echo views inside a sort-nest +CREATE TABLE t0 (x int); +CREATE TABLE t1 (a int); +CREATE TABLE t2 (b int, c int default 0); + +INSERT t0 (x) VALUES (0),(10); +INSERT t1 (a) VALUES (1), (2); +INSERT t2 (b) VALUES (1), (2); + +CREATE VIEW v1 as SELECT t2.b,t2.c FROM t1, t2 + WHERE t1.a=t2.b and t2.b < 3 WITH CHECK OPTION; + +let $query= SELECT * FROM v1,t0 + WHERE b<3 + ORDER BY x,b DESC + LIMIT 2; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; +DROP TABLE t0,t1,t2; +DROP VIEW v1; + +--echo # Primary key considered as the key that could achieve ordering + +CREATE TABLE t1 (id char(32) NOT NULL primary key); +INSERT INTO t1 VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9); +CREATE TABLE t2 (id char(32) NOT NULL primary key); +INSERT INTO t2 VALUES (0), (1), (2), (3); + +let $query= SELECT t1.id + FROM t1 INNER JOIN t2 ON t1.id=t2.id + ORDER BY t2.id LIMIT 2; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +DROP TABLE t1,t2; + +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT seq-1, seq-1 from seq_1_to_10; +CREATE TABLE t2 as SELECT * from t1; + +CREATE TABLE t3 (a int, b int); +INSERT INTO t3 SELECT seq-1, seq-1 from seq_1_to_1000; + +--disable_result_log +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +--enable_result_log + +let $query= SELECT * from t2,t1 left join t3 on t3.a=t1.b + order by t2.a desc,t1.a desc limit 5; + +--echo # +--echo # Outer Join +--echo # + +--echo # sort-nest(t2,t1) and t3 outside the nest +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo # +--echo # no sort-nest as all the inner tables of the outer join will be +--echo # inside the nest, this should use temporary table to sort after the +--echo # entire join is computed +--echo # + +let $query= SELECT * FROM t2 LEFT JOIN (t1 LEFT JOIN t3 ON t3.a=t1.b) + ON t2.b=t1.a + ORDER BY t2.a DESC,t1.a DESC LIMIT 5; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; +DROP TABLE t1,t2,t3; + +--echo # +--echo # Sort-nest with prepared statements +--echo # + +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT seq-1,seq-1 from seq_1_to_10; +CREATE TABLE t2 as SELECT * from t1; + +CREATE TABLE t3 (a int, b int); +INSERT INTO t3 SELECT seq-1, seq-1 from seq_1_to_1000; + +--disable_result_log +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +--enable_result_log + +--echo # sort-nest on table t1,t2 +prepare ps1 from "EXPLAIN SELECT * FROM t2,t1,t3 + WHERE t3.a=t1.b+1 + ORDER BY t2.a DESC,t1.a DESC + LIMIT 5"; + +EXECUTE ps1; +EXECUTE ps1; + +--echo # sort-nest on table t1,t2 +PREPARE ps2 from "EXPLAIN FORMAT=JSON + SELECT * from t2,t1,t3 + WHERE t3.a=t1.b+1 + ORDER BY t2.a DESC, t1.a DESC + LIMIT 5"; +EXECUTE ps2; +EXECUTE ps2; + +--echo # sort-nest on table t1,t2 +PREPARE ps3 from "SELECT * from t2,t1,t3 + WHERE t3.a=t1.b+1 + ORDER BY t2.a DESC, t1.a DESC + LIMIT 5"; +EXECUTE ps3; +EXECUTE ps3; + +DEALLOCATE PREPARE ps1; +DEALLOCATE PREPARE ps2; +DEALLOCATE PREPARE ps3; +DROP TABLE t1,t2,t3; + +--echo # INDEPENDENT SUBQUERIES + +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a,a from t0 where a <5; +CREATE TABLE t2 as SELECT * from t1 where a < 5; +CREATE TABLE t3 (a int, b int, c int); +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 from seq_1_to_1000; + +--disable_result_log +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +--enable_result_log + +--echo # +--echo # sort-nest(t2,t1) and independent subquery in the SELECT list +--echo # + +let $query= SELECT (SELECT A.a FROM t3 A WHERE A.a > 5 limit 1) as x, + t2.b, t1.b, t3.a + FROM t1,t2,t3 + WHERE t1.a = t2.a + ORDER BY t2.b DESC, t1.b DESC + LIMIT 5; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +DROP TABLE t0,t1,t2,t3; + +--echo # +--echo # Const table should not form the sort-nest +--echo # + +CREATE TABLE t1 (i1 integer NOT NULL PRIMARY KEY); +CREATE TABLE t2 (i2 integer NOT NULL PRIMARY KEY); +CREATE TABLE t3 (i3 integer); +INSERT INTO t1 VALUES (1), (2), (3), (4), (5), (6), (7), (8), + (9), (10), (11), (12); + +INSERT INTO t2 SELECT * FROM t1; + +let $query= SELECT t1.*, t2.* + FROM t1 JOIN t2 ON t1.i1 = t2.i2 + LEFT JOIN t3 ON t2.i2 = t3.i3 + ORDER BY t1.i1 + LIMIT 5; + +eval EXPLAIN $query; +eval $query; + +DROP TABLE t1,t2,t3; + +--echo # All tables are const tables + +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 VALUES (0,0); +CREATE TABLE t2(a int, b int); +INSERT INTO t2 VALUES (0,0); +CREATE TABLE t3(a int, b int); +INSERT INTO t3 VALUES (0,0); + +let $query= SELECT t1.a,t2.a,t3.a + FROM t1,t2,t3 + WHERE t1.b = t2.b AND t3.b=t1.b + ORDER BY t2.a DESC,t1.a DESC + LIMIT 5; + +eval EXPLAIN $query; +eval $query; + +DROP TABLE t1,t2,t3; + +--echo # +--echo # Tests where Index(scan, ref or range access) satisfies the ORDERING +--echo # + +CREATE TABLE t1 (a int, b int, c int, KEY a_b (a,b), KEY a_c (a,c)); + +INSERT INTO t1 VALUES (0,1,0), (0,2,0), (0,3,0), (0,4,0), (0,5,0), (0,6,0); +INSERT INTO t1 values (1,7,1), (1,8,1), (1,9,1), (1,10,1), (1,11,1), (1,12,1); +INSERT INTO t1 VALUES (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +INSERT INTO t1 VALUES (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +INSERT INTO t1 VALUES (1,1,2); + +--echo # index key a_b, no need for filesort + +let $query= SELECT a,b,c FROM t1 + WHERE a=1 and c=2 + ORDER BY b + LIMIT 10; + +eval EXPLAIN $query; +eval $query; +DROP TABLE t1; + +--echo # +--echo # Testing ORDER BY LIMIT with OFFSET, should show the same plan and same +--echo # estimate of rows for the sort-nest +--echo # + +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a, a from t0; +CREATE TABLE t2 as SELECT * from t1; +CREATE TABLE t3(a int, b int, c int, key(a)); +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 from seq_1_to_100; + +--disable_result_log +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +--enable_result_log + +let $query= SELECT t1.a, t2.b, t1.b, t3.a + FROM t1,t2,t3 + WHERE t1.a=t2.a AND t3.a = t2.a + ORDER BY t2.b DESC, t1.b DESC + LIMIT 10; +eval EXPLAIN $query; +eval $query; + +let $query= SELECT t1.a, t2.b, t1.b, t3.a + FROM t1,t2,t3 + WHERE t1.a=t2.a AND t3.a=t2.a + ORDER BY t2.b DESC, t1.b DESC + LIMIT 5 OFFSET 5; + +eval EXPLAIN $query; +eval $query; + +drop table t0,t1,t2,t3; + + +--echo # +--echo # Constant removed from ORDER BY , so no need of sorting +--echo # + +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a, a from t0; +CREATE TABLE t2 as SELECT * from t1; + +let $query= SELECT * + FROM t1,t2 + WHERE t1.a=t2.a and t1.b= 4 + ORDER BY t1.b DESC + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval $query; + +drop table t0,t1,t2; + +--echo # +--echo # ORDER BY clause containing expressions +--echo # + +CREATE TABLE t0 (a int); +INSERT INTO t0 SELECT seq-1 from seq_1_to_10; +CREATE TABLE t1 (a int, b int); +INSERT INTO t1 SELECT a, a from t0 where a <5; +CREATE TABLE t2 as SELECT * from t1 where a < 5; +CREATE TABLE t3(a int, b int, c int, key(a)); +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 from seq_1_to_10; + +--disable_result_log +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +--enable_result_log + + +let $query= SELECT t1.a, t2.b, t1.b, t1.b + t2.b + FROM t1,t2, t3 + WHERE t1.a=t2.b and t2.a=t3.a + ORDER BY abs(t3.a+t1.b) DESC + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval $query; + +set use_sort_nest=0; +eval $query; + +--echo # +--echo # No sort nest where ORDER BY item are expensive to compute like +--echo # stored functions, subqueries etc + +delimiter |; + +CREATE FUNCTION f1(a INT) RETURNS INT +BEGIN + RETURN a; +END| + +delimiter ;| + +let $query= SELECT t1.a, t2.b, t1.b, t1.b + t2.b + FROM t1,t2, t3 + WHERE t1.a=t2.b and t2.a=t3.a + ORDER BY f1(t3.a+t1.b) DESC + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set use_sort_nest=0; +eval EXPLAIN $query; +eval $query; + +drop function f1; + +--echo # +--echo # Window function in order by clause, sort-nest not allowed +--echo # + +let $query= SELECT t1.a, t2.b, t1.b, t1.b + t2.b + FROM t1,t2, t3 + WHERE t1.a=t2.b AND t2.a=t3.a + ORDER BY row_number() OVER (ORDER BY t1.a) DESC + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set use_sort_nest=0; +eval $query; + +--echo # +--echo # Subqueries used in order by clause +--echo # + +let $query= SELECT t1.a, t2.b, t1.b, t1.b + t2.b + FROM t1,t2, t3 + WHERE t1.a=t2.b and t2.a=t3.a + ORDER BY (select A.a+t1.b from t1 A limit 1) + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set use_sort_nest=0; +eval $query; + +drop table t0,t1,t2,t3; diff --git a/mysql-test/main/sort_nest_dbt3.result b/mysql-test/main/sort_nest_dbt3.result new file mode 100644 index 00000000000..6143ede4d1b --- /dev/null +++ b/mysql-test/main/sort_nest_dbt3.result @@ -0,0 +1,610 @@ +create database dbt3; +use dbt3; +Table Op Msg_type Msg_text +dbt3.customer analyze status Engine-independent statistics collected +dbt3.customer analyze status OK +Table Op Msg_type Msg_text +dbt3.orders analyze status Engine-independent statistics collected +dbt3.orders analyze status OK +Table Op Msg_type Msg_text +dbt3.lineitem analyze status Engine-independent statistics collected +dbt3.lineitem analyze status OK +Table Op Msg_type Msg_text +dbt3.nation analyze status Engine-independent statistics collected +dbt3.nation analyze status OK +# done to avoid filter for now +set optimizer_switch='rowid_filter=off'; +set use_sort_nest=1; +# +# USING INDEXES FOR ORDERING +# +# +# Using index scan on first table +# +set use_sort_nest=1; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey +ORDER BY o_orderDATE DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders index i_o_custkey i_o_orderdate 4 NULL 5 Using where +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey +ORDER BY o_orderDATE DESC +LIMIT 5; +c_nationkey c_name o_orderDate o_totalprice c_acctbal +16 Customer#000000088 1998-08-02 131752.07 8031.44 +0 Customer#000000080 1998-07-30 141858.97 7383.53 +10 Customer#000000049 1998-07-29 37776.79 4573.94 +3 Customer#000000022 1998-07-28 139104.17 591.98 +3 Customer#000000022 1998-07-27 82746.74 591.98 +set use_sort_nest=0; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey +ORDER BY o_orderDATE DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders index i_o_custkey i_o_orderdate 4 NULL 5 Using where +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey +ORDER BY o_orderDATE DESC +LIMIT 5; +c_nationkey c_name o_orderDate o_totalprice c_acctbal +16 Customer#000000088 1998-08-02 131752.07 8031.44 +0 Customer#000000080 1998-07-30 141858.97 7383.53 +10 Customer#000000049 1998-07-29 37776.79 4573.94 +3 Customer#000000022 1998-07-28 139104.17 591.98 +3 Customer#000000022 1998-07-27 82746.74 591.98 +# +# Using range access for customer with index on (c), this does +# the ordering +# +alter table orders add key o_orderdate(o_orderDATE, o_custkey); +set use_sort_nest=1; +explain SELECT +o_custkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey AND +o_orderDATE >='1997-07-30' and o_orderDATE <= '1998-07-30' +ORDER BY o_orderDATE, o_custkey DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey,o_orderdate i_o_orderdate 4 NULL 242 Using index condition; Using where; Using filesort +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +o_custkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey AND +o_orderDATE >='1997-07-30' and o_orderDATE <= '1998-07-30' +ORDER BY o_orderDATE, o_custkey DESC +LIMIT 5; +o_custkey c_name o_orderDate o_totalprice c_acctbal +130 Customer#000000130 1997-07-30 67979.49 5073.58 +101 Customer#000000101 1997-07-31 79189.58 7470.96 +13 Customer#000000013 1997-07-31 125188.72 3857.34 +64 Customer#000000064 1997-08-03 76164.41 -646.64 +50 Customer#000000050 1997-08-03 20791.5 4266.13 +set use_sort_nest=0; +explain SELECT +o_custkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey AND +o_orderDATE >='1997-07-30' and o_orderDATE <= '1998-07-30' +ORDER BY o_orderDATE, o_custkey DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey,o_orderdate i_o_orderdate 4 NULL 242 Using index condition; Using where; Using filesort +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +o_custkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey AND +o_orderDATE >='1997-07-30' and o_orderDATE <= '1998-07-30' +ORDER BY o_orderDATE, o_custkey DESC +LIMIT 5; +o_custkey c_name o_orderDate o_totalprice c_acctbal +130 Customer#000000130 1997-07-30 67979.49 5073.58 +101 Customer#000000101 1997-07-31 79189.58 7470.96 +13 Customer#000000013 1997-07-31 125188.72 3857.34 +64 Customer#000000064 1997-08-03 76164.41 -646.64 +50 Customer#000000050 1997-08-03 20791.5 4266.13 +alter table orders drop key o_orderDate; +# +# USING FILESORT +# +# +# Filesort on first table (orders) +# +set use_sort_nest=1; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-01-30' and o_orderDATE <= '1997-12-31' +ORDER BY o_totalprice DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey i_o_orderdate 4 NULL 197 Using index condition; Using where; Using filesort +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 Using where +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 dbt3.customer.c_nationkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-01-30' and o_orderDATE <= '1997-12-31' +ORDER BY o_totalprice DESC +LIMIT 10; +c_nationkey c_name o_orderDate o_totalprice c_acctbal n_name +5 Customer#000000010 1997-04-04 258779.02 2753.54 ETHIOPIA +0 Customer#000000076 1997-08-24 231831.35 5745.33 ALGERIA +9 Customer#000000094 1997-10-17 224382.57 5500.11 INDONESIA +6 Customer#000000046 1997-05-26 216826.73 5744.59 FRANCE +3 Customer#000000005 1997-04-23 210643.96 794.47 CANADA +11 Customer#000000148 1997-07-25 206179.68 2135.6 IRAQ +2 Customer#000000101 1997-07-28 204163.1 7470.96 BRAZIL +3 Customer#000000005 1997-11-09 190142.17 794.47 CANADA +9 Customer#000000094 1997-11-30 187516.29 5500.11 INDONESIA +3 Customer#000000005 1997-11-12 185496.66 794.47 CANADA +set use_sort_nest=0; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-01-30' and o_orderDATE <= '1997-12-31' +ORDER BY o_totalprice DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey i_o_orderdate 4 NULL 197 Using index condition; Using where; Using filesort +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 Using where +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 dbt3.customer.c_nationkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-01-30' and o_orderDATE <= '1997-12-31' +ORDER BY o_totalprice DESC +LIMIT 10; +c_nationkey c_name o_orderDate o_totalprice c_acctbal n_name +5 Customer#000000010 1997-04-04 258779.02 2753.54 ETHIOPIA +0 Customer#000000076 1997-08-24 231831.35 5745.33 ALGERIA +9 Customer#000000094 1997-10-17 224382.57 5500.11 INDONESIA +6 Customer#000000046 1997-05-26 216826.73 5744.59 FRANCE +3 Customer#000000005 1997-04-23 210643.96 794.47 CANADA +11 Customer#000000148 1997-07-25 206179.68 2135.6 IRAQ +2 Customer#000000101 1997-07-28 204163.1 7470.96 BRAZIL +3 Customer#000000005 1997-11-09 190142.17 794.47 CANADA +9 Customer#000000094 1997-11-30 187516.29 5500.11 INDONESIA +3 Customer#000000005 1997-11-12 185496.66 794.47 CANADA +# +# Filesort on first table (lineitem) +# +show create table orders; +Table Create Table +orders CREATE TABLE `orders` ( + `o_orderkey` int(11) NOT NULL, + `o_custkey` int(11) DEFAULT NULL, + `o_orderstatus` char(1) DEFAULT NULL, + `o_totalprice` double DEFAULT NULL, + `o_orderDATE` date DEFAULT NULL, + `o_orderpriority` char(15) DEFAULT NULL, + `o_clerk` char(15) DEFAULT NULL, + `o_shippriority` int(11) DEFAULT NULL, + `o_comment` varchar(79) DEFAULT NULL, + PRIMARY KEY (`o_orderkey`), + KEY `i_o_orderdate` (`o_orderDATE`), + KEY `i_o_custkey` (`o_custkey`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +set use_sort_nest=1; +explain SELECT +l_extendedprice, o_orderkey, o_totalprice, l_shipDATE +FROM +orders, lineitem +WHERE +o_orderkey = l_orderkey AND +l_shipDATE >= '1996-01-01' AND l_shipDATE <= '1996-12-31' + ORDER BY l_extendedprice DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE lineitem range PRIMARY,i_l_shipdate,i_l_orderkey,i_l_orderkey_quantity i_l_shipdate 4 NULL 812 Using index condition; Using filesort +1 SIMPLE orders eq_ref PRIMARY PRIMARY 4 dbt3.lineitem.l_orderkey 1 +SELECT +l_extendedprice, o_orderkey, o_totalprice, l_shipDATE +FROM +orders, lineitem +WHERE +o_orderkey = l_orderkey AND +l_shipDATE >= '1996-01-01' AND l_shipDATE <= '1996-12-31' + ORDER BY l_extendedprice DESC +LIMIT 10; +l_extendedprice o_orderkey o_totalprice l_shipDATE +54559.5 1574 179923.54 1996-12-14 +53508.5 2342 104038.78 1996-08-28 +53458 1153 220727.97 1996-06-27 +53075.82 2721 59180.25 1996-02-14 +52830.33 1284 106122.38 1996-04-11 +52657.5 2276 141159.63 1996-07-13 +52290.84 4773 196080.26 1996-01-26 +51752.16 1607 166335.03 1996-02-22 +51751.35 1700 89143.36 1996-09-26 +51709.4 1254 94649.25 1996-03-07 +set use_sort_nest=0; +explain SELECT +l_extendedprice, o_orderkey, o_totalprice, l_shipDATE +FROM +orders, lineitem +WHERE +o_orderkey = l_orderkey AND +l_shipDATE >= '1996-01-01' AND l_shipDATE <= '1996-12-31' + ORDER BY l_extendedprice DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE lineitem range PRIMARY,i_l_shipdate,i_l_orderkey,i_l_orderkey_quantity i_l_shipdate 4 NULL 812 Using index condition; Using filesort +1 SIMPLE orders eq_ref PRIMARY PRIMARY 4 dbt3.lineitem.l_orderkey 1 +SELECT +l_extendedprice, o_orderkey, o_totalprice, l_shipDATE +FROM +orders, lineitem +WHERE +o_orderkey = l_orderkey AND +l_shipDATE >= '1996-01-01' AND l_shipDATE <= '1996-12-31' + ORDER BY l_extendedprice DESC +LIMIT 10; +l_extendedprice o_orderkey o_totalprice l_shipDATE +54559.5 1574 179923.54 1996-12-14 +53508.5 2342 104038.78 1996-08-28 +53458 1153 220727.97 1996-06-27 +53075.82 2721 59180.25 1996-02-14 +52830.33 1284 106122.38 1996-04-11 +52657.5 2276 141159.63 1996-07-13 +52290.84 4773 196080.26 1996-01-26 +51752.16 1607 166335.03 1996-02-22 +51751.35 1700 89143.36 1996-09-26 +51709.4 1254 94649.25 1996-03-07 +# +# Using Filesort with Sort Nest +# +# +# FILESORT USING SORT-NEST on (orders, customer) +# +set use_sort_nest=1; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-07-01' and o_orderDATE <= '1997-07-31' +ORDER BY o_orderDATE DESC, c_acctbal DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey i_o_orderdate 4 NULL 20 Using index condition; Using where +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 10 Using filesort +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 sort-nest.c_nationkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-07-01' and o_orderDATE <= '1997-07-31' +ORDER BY o_orderDATE DESC, c_acctbal DESC +LIMIT 10; +c_nationkey c_name o_orderDate o_totalprice c_acctbal n_name +2 Customer#000000101 1997-07-31 79189.58 7470.96 BRAZIL +3 Customer#000000013 1997-07-31 125188.72 3857.34 CANADA +9 Customer#000000130 1997-07-30 67979.49 5073.58 INDONESIA +11 Customer#000000148 1997-07-29 88448.24 2135.6 IRAQ +2 Customer#000000101 1997-07-28 204163.1 7470.96 BRAZIL +11 Customer#000000052 1997-07-28 46393.97 5630.28 IRAQ +4 Customer#000000004 1997-07-25 11405.4 2866.83 EGYPT +11 Customer#000000148 1997-07-25 206179.68 2135.6 IRAQ +18 Customer#000000082 1997-07-20 15082.82 9468.34 CHINA +10 Customer#000000055 1997-07-16 46380.69 4572.11 IRAN +set use_sort_nest=0; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-07-01' and o_orderDATE <= '1997-07-31' +ORDER BY o_orderDATE DESC, c_acctbal DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey i_o_orderdate 4 NULL 20 Using index condition; Using where; Using temporary; Using filesort +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 Using where +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 dbt3.customer.c_nationkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-07-01' and o_orderDATE <= '1997-07-31' +ORDER BY o_orderDATE DESC, c_acctbal DESC +LIMIT 10; +c_nationkey c_name o_orderDate o_totalprice c_acctbal n_name +2 Customer#000000101 1997-07-31 79189.58 7470.96 BRAZIL +3 Customer#000000013 1997-07-31 125188.72 3857.34 CANADA +9 Customer#000000130 1997-07-30 67979.49 5073.58 INDONESIA +11 Customer#000000148 1997-07-29 88448.24 2135.6 IRAQ +2 Customer#000000101 1997-07-28 204163.1 7470.96 BRAZIL +11 Customer#000000052 1997-07-28 46393.97 5630.28 IRAQ +4 Customer#000000004 1997-07-25 11405.4 2866.83 EGYPT +11 Customer#000000148 1997-07-25 206179.68 2135.6 IRAQ +18 Customer#000000082 1997-07-20 15082.82 9468.34 CHINA +10 Customer#000000055 1997-07-16 46380.69 4572.11 IRAN +# +# Sort-nest on table (lineitem, customer) +# +set use_sort_nest=1; +explain SELECT +c_acctbal, c_name, o_orderDate, l_extendedprice, l_orderkey +FROM +orders, customer, lineitem +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +l_orderkey between 5500 AND 6000 +AND +l_shipDATE >= '1997-01-01' and l_shipDATE <= '1998-08-10' + ORDER BY l_extendedprice DESC, c_acctbal DESC +LIMIT 20; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE lineitem range PRIMARY,i_l_shipdate,i_l_orderkey,i_l_orderkey_quantity i_l_orderkey_quantity 4 NULL 330 Using index condition; Using where; Using temporary; Using filesort +1 SIMPLE orders eq_ref PRIMARY,i_o_custkey PRIMARY 4 dbt3.lineitem.l_orderkey 1 Using where +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +c_acctbal, c_name, o_orderDate, l_extendedprice, l_orderkey +FROM +orders, customer, lineitem +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +l_orderkey between 5500 AND 6000 +AND +l_shipDATE >= '1997-01-01' and l_shipDATE <= '1998-08-10' + ORDER BY l_extendedprice DESC, c_acctbal DESC +LIMIT 20; +c_acctbal c_name o_orderDate l_extendedprice l_orderkey +1842.49 Customer#000000124 1997-11-06 54759.5 5857 +2135.6 Customer#000000148 1997-04-14 53909.8 5952 +4681.03 Customer#000000016 1998-07-06 53811.31 5761 +794.47 Customer#000000005 1997-04-23 53758.5 5859 +-234.12 Customer#000000125 1997-01-11 53468.31 5829 +5121.28 Customer#000000079 1998-05-31 53208 5633 +5744.59 Customer#000000046 1998-04-14 50770.37 5604 +-917.75 Customer#000000037 1997-07-13 50310.72 5793 +121.65 Customer#000000002 1998-05-28 49830.24 5507 +121.65 Customer#000000002 1998-05-28 49542.24 5507 +7470.96 Customer#000000101 1997-05-27 49411.82 5923 +5327.38 Customer#000000095 1997-10-04 48859.36 5505 +1842.49 Customer#000000124 1997-11-06 48661.41 5857 +4573.94 Customer#000000049 1997-02-14 48557.11 5762 +-646.64 Customer#000000064 1997-01-01 48219.92 5895 +-646.64 Customer#000000064 1997-01-01 48039.64 5895 +5121.28 Customer#000000079 1998-05-31 48004.8 5633 +9904.28 Customer#000000043 1998-02-06 47339.52 5671 +8959.65 Customer#000000149 1996-11-12 47247.52 5606 +5744.59 Customer#000000046 1998-04-14 45589.72 5604 +set use_sort_nest=0; +explain SELECT +c_acctbal, c_name, o_orderDate, l_extendedprice, l_orderkey +FROM +orders, customer, lineitem +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +l_orderkey between 5500 AND 6000 +AND +l_shipDATE >= '1997-01-01' and l_shipDATE <= '1998-08-10' + ORDER BY l_extendedprice DESC, c_acctbal DESC +LIMIT 20; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE lineitem range PRIMARY,i_l_shipdate,i_l_orderkey,i_l_orderkey_quantity i_l_orderkey_quantity 4 NULL 330 Using index condition; Using where; Using temporary; Using filesort +1 SIMPLE orders eq_ref PRIMARY,i_o_custkey PRIMARY 4 dbt3.lineitem.l_orderkey 1 Using where +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +c_acctbal, c_name, o_orderDate, l_extendedprice, l_orderkey +FROM +orders, customer, lineitem +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +l_orderkey between 5500 AND 6000 +AND +l_shipDATE >= '1997-01-01' and l_shipDATE <= '1998-08-10' + ORDER BY l_extendedprice DESC, c_acctbal DESC +LIMIT 20; +c_acctbal c_name o_orderDate l_extendedprice l_orderkey +1842.49 Customer#000000124 1997-11-06 54759.5 5857 +2135.6 Customer#000000148 1997-04-14 53909.8 5952 +4681.03 Customer#000000016 1998-07-06 53811.31 5761 +794.47 Customer#000000005 1997-04-23 53758.5 5859 +-234.12 Customer#000000125 1997-01-11 53468.31 5829 +5121.28 Customer#000000079 1998-05-31 53208 5633 +5744.59 Customer#000000046 1998-04-14 50770.37 5604 +-917.75 Customer#000000037 1997-07-13 50310.72 5793 +121.65 Customer#000000002 1998-05-28 49830.24 5507 +121.65 Customer#000000002 1998-05-28 49542.24 5507 +7470.96 Customer#000000101 1997-05-27 49411.82 5923 +5327.38 Customer#000000095 1997-10-04 48859.36 5505 +1842.49 Customer#000000124 1997-11-06 48661.41 5857 +4573.94 Customer#000000049 1997-02-14 48557.11 5762 +-646.64 Customer#000000064 1997-01-01 48219.92 5895 +-646.64 Customer#000000064 1997-01-01 48039.64 5895 +5121.28 Customer#000000079 1998-05-31 48004.8 5633 +9904.28 Customer#000000043 1998-02-06 47339.52 5671 +8959.65 Customer#000000149 1996-11-12 47247.52 5606 +5744.59 Customer#000000046 1998-04-14 45589.72 5604 +######################################################################## +# +# Sort-nest on table (customer, orders, lineitem) +# +set use_sort_nest=1; +explain SELECT +l_extendedprice, c_acctbal, o_totalprice, o_orderDate, l_orderkey +FROM +orders, customer, lineitem, nation +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +n_nationkey = c_nationkey +AND +l_orderkey between 5500 AND 6000 +AND +c_custkey between 1 and 10 +ORDER BY +l_extendedprice DESC, c_acctbal DESC, o_totalprice DESC +LIMIT 20; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE customer range PRIMARY,i_c_nationkey PRIMARY 4 NULL 7 Using index condition +1 SIMPLE orders ref PRIMARY,i_o_custkey i_o_custkey 5 dbt3.customer.c_custkey 15 Using where +1 SIMPLE lineitem ref PRIMARY,i_l_orderkey,i_l_orderkey_quantity PRIMARY 4 dbt3.orders.o_orderkey 4 +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 20 Using filesort +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 sort-nest.c_nationkey 1 Using index +SELECT +l_extendedprice, c_acctbal, o_totalprice, o_orderDate, l_orderkey +FROM +orders, customer, lineitem, nation +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +n_nationkey = c_nationkey +AND +l_orderkey between 5500 AND 6000 +AND +c_custkey between 1 and 10 +ORDER BY +l_extendedprice DESC, c_acctbal DESC, o_totalprice DESC +LIMIT 20; +l_extendedprice c_acctbal o_totalprice o_orderDate l_orderkey +53758.5 794.47 210643.96 1997-04-23 5859 +49830.24 121.65 140363.7 1998-05-28 5507 +49542.24 121.65 140363.7 1998-05-28 5507 +48745.11 6819.74 122823.78 1993-04-05 5794 +47992.64 6819.74 140838.11 1998-06-26 5763 +47615.98 6819.74 182966.39 1994-07-17 5572 +46705.74 9561.95 101429.61 1993-04-21 5670 +44467.59 121.65 44777.63 1992-07-08 5893 +44442.3 6819.74 122823.78 1993-04-05 5794 +39723.6 794.47 210643.96 1997-04-23 5859 +37048.32 9561.95 95312.81 1992-03-28 5953 +36860.25 794.47 210643.96 1997-04-23 5859 +32996.16 6819.74 140838.11 1998-06-26 5763 +31416.68 6819.74 182966.39 1994-07-17 5572 +31219.32 794.47 210643.96 1997-04-23 5859 +31042.34 9561.95 95312.81 1992-03-28 5953 +29462.13 794.47 210643.96 1997-04-23 5859 +28948.59 6819.74 182966.39 1994-07-17 5572 +26732.43 9561.95 101429.61 1993-04-21 5670 +24590.68 9561.95 95312.81 1992-03-28 5953 +set use_sort_nest=0; +explain SELECT +l_extendedprice, c_acctbal, o_totalprice, o_orderDate, l_orderkey +FROM +orders, customer, lineitem, nation +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +n_nationkey = c_nationkey +AND +l_orderkey between 5500 AND 6000 +AND +c_custkey between 1 and 10 +ORDER BY +l_extendedprice DESC, c_acctbal DESC, o_totalprice DESC +LIMIT 20; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE customer range PRIMARY,i_c_nationkey PRIMARY 4 NULL 7 Using index condition; Using where; Using temporary; Using filesort +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 dbt3.customer.c_nationkey 1 Using index +1 SIMPLE orders ref PRIMARY,i_o_custkey i_o_custkey 5 dbt3.customer.c_custkey 15 Using where +1 SIMPLE lineitem ref PRIMARY,i_l_orderkey,i_l_orderkey_quantity PRIMARY 4 dbt3.orders.o_orderkey 4 +SELECT +l_extendedprice, c_acctbal, o_totalprice, o_orderDate, l_orderkey +FROM +orders, customer, lineitem, nation +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +n_nationkey = c_nationkey +AND +l_orderkey between 5500 AND 6000 +AND +c_custkey between 1 and 10 +ORDER BY +l_extendedprice DESC, c_acctbal DESC, o_totalprice DESC +LIMIT 20; +l_extendedprice c_acctbal o_totalprice o_orderDate l_orderkey +53758.5 794.47 210643.96 1997-04-23 5859 +49830.24 121.65 140363.7 1998-05-28 5507 +49542.24 121.65 140363.7 1998-05-28 5507 +48745.11 6819.74 122823.78 1993-04-05 5794 +47992.64 6819.74 140838.11 1998-06-26 5763 +47615.98 6819.74 182966.39 1994-07-17 5572 +46705.74 9561.95 101429.61 1993-04-21 5670 +44467.59 121.65 44777.63 1992-07-08 5893 +44442.3 6819.74 122823.78 1993-04-05 5794 +39723.6 794.47 210643.96 1997-04-23 5859 +37048.32 9561.95 95312.81 1992-03-28 5953 +36860.25 794.47 210643.96 1997-04-23 5859 +32996.16 6819.74 140838.11 1998-06-26 5763 +31416.68 6819.74 182966.39 1994-07-17 5572 +31219.32 794.47 210643.96 1997-04-23 5859 +31042.34 9561.95 95312.81 1992-03-28 5953 +29462.13 794.47 210643.96 1997-04-23 5859 +28948.59 6819.74 182966.39 1994-07-17 5572 +26732.43 9561.95 101429.61 1993-04-21 5670 +24590.68 9561.95 95312.81 1992-03-28 5953 +drop database dbt3; diff --git a/mysql-test/main/sort_nest_dbt3.test b/mysql-test/main/sort_nest_dbt3.test new file mode 100644 index 00000000000..503bed0733a --- /dev/null +++ b/mysql-test/main/sort_nest_dbt3.test @@ -0,0 +1,216 @@ +create database dbt3; +use dbt3; +--disable_query_log +--source include/dbt3_s001.inc + + +analyze table customer persistent for all; +analyze table orders persistent for all; +analyze table lineitem persistent for all; +analyze table nation persistent for all; +--enable_query_log + +--echo # done to avoid filter for now +set optimizer_switch='rowid_filter=off'; +set use_sort_nest=1; + +--echo # +--echo # USING INDEXES FOR ORDERING +--echo # + + +--echo # +--echo # Using index scan on first table +--echo # + +let $query= SELECT + c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal + FROM + orders, customer + WHERE + c_custkey= o_custkey + ORDER BY o_orderDATE DESC + LIMIT 5; + +set use_sort_nest=1; +eval explain $query; +eval $query; + +set use_sort_nest=0; +eval explain $query; +eval $query; + +--echo # +--echo # Using range access for customer with index on (c), this does +--echo # the ordering +--echo # + +alter table orders add key o_orderdate(o_orderDATE, o_custkey); + +let $query= SELECT + o_custkey, c_name, o_orderDate, o_totalprice, c_acctbal + FROM + orders, customer + WHERE + c_custkey= o_custkey AND + o_orderDATE >='1997-07-30' and o_orderDATE <= '1998-07-30' + ORDER BY o_orderDATE, o_custkey DESC + LIMIT 5; + +set use_sort_nest=1; +eval explain $query; +eval $query; + +set use_sort_nest=0; +eval explain $query; +eval $query; + +alter table orders drop key o_orderDate; + +############################################################################### + +--echo # +--echo # USING FILESORT +--echo # + +--echo # +--echo # Filesort on first table (orders) +--echo # + +let $query= SELECT + c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name + FROM + orders, customer, nation + WHERE + c_custkey= o_custkey AND c_nationkey=n_nationkey AND + o_orderDATE >='1997-01-30' and o_orderDATE <= '1997-12-31' + ORDER BY o_totalprice DESC + LIMIT 10; + +set use_sort_nest=1; +eval explain $query; +eval $query; + +set use_sort_nest=0; +eval explain $query; +eval $query; + + +--echo # +--echo # Filesort on first table (lineitem) +--echo # + +show create table orders; + +let $query= SELECT + l_extendedprice, o_orderkey, o_totalprice, l_shipDATE + FROM + orders, lineitem + WHERE + o_orderkey = l_orderkey AND + l_shipDATE >= '1996-01-01' AND l_shipDATE <= '1996-12-31' + ORDER BY l_extendedprice DESC + LIMIT 10; + +set use_sort_nest=1; +eval explain $query; +eval $query; + +set use_sort_nest=0; +eval explain $query; +eval $query; + +############################################################################### + + +--echo # +--echo # Using Filesort with Sort Nest +--echo # + +--echo # +--echo # FILESORT USING SORT-NEST on (orders, customer) +--echo # + +let $query= SELECT + c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name + FROM + orders, customer, nation + WHERE + c_custkey= o_custkey AND c_nationkey=n_nationkey AND + o_orderDATE >='1997-07-01' and o_orderDATE <= '1997-07-31' + ORDER BY o_orderDATE DESC, c_acctbal DESC + LIMIT 10; + +set use_sort_nest=1; +eval explain $query; +eval $query; + +set use_sort_nest=0; +eval explain $query; +eval $query; + + + +--echo # +--echo # Sort-nest on table (lineitem, customer) +--echo # + +let $query= SELECT + c_acctbal, c_name, o_orderDate, l_extendedprice, l_orderkey + FROM + orders, customer, lineitem + WHERE + c_custkey= o_custkey + AND + l_orderkey = o_orderkey + AND + l_orderkey between 5500 AND 6000 + AND + l_shipDATE >= '1997-01-01' and l_shipDATE <= '1998-08-10' + ORDER BY l_extendedprice DESC, c_acctbal DESC + LIMIT 20; + + +set use_sort_nest=1; +eval explain $query; +eval $query; + +set use_sort_nest=0; +eval explain $query; +eval $query; + +--echo ######################################################################## + +--echo # +--echo # Sort-nest on table (customer, orders, lineitem) +--echo # + +let $query= SELECT + l_extendedprice, c_acctbal, o_totalprice, o_orderDate, l_orderkey + FROM + orders, customer, lineitem, nation + WHERE + c_custkey= o_custkey + AND + l_orderkey = o_orderkey + AND + n_nationkey = c_nationkey + AND + l_orderkey between 5500 AND 6000 + AND + c_custkey between 1 and 10 + ORDER BY + l_extendedprice DESC, c_acctbal DESC, o_totalprice DESC + LIMIT 20; + + +set use_sort_nest=1; +eval explain $query; +eval $query; + +set use_sort_nest=0; +eval explain $query; +eval $query; + + +drop database dbt3; diff --git a/mysql-test/main/sort_nest_dbt3_innodb.result b/mysql-test/main/sort_nest_dbt3_innodb.result new file mode 100644 index 00000000000..9c4c8144225 --- /dev/null +++ b/mysql-test/main/sort_nest_dbt3_innodb.result @@ -0,0 +1,611 @@ +SET SESSION STORAGE_ENGINE='InnoDB'; +create database dbt3; +use dbt3; +Table Op Msg_type Msg_text +dbt3.customer analyze status Engine-independent statistics collected +dbt3.customer analyze status OK +Table Op Msg_type Msg_text +dbt3.orders analyze status Engine-independent statistics collected +dbt3.orders analyze status OK +Table Op Msg_type Msg_text +dbt3.lineitem analyze status Engine-independent statistics collected +dbt3.lineitem analyze status OK +Table Op Msg_type Msg_text +dbt3.nation analyze status Engine-independent statistics collected +dbt3.nation analyze status OK +# done to avoid filter for now +set optimizer_switch='rowid_filter=off'; +set use_sort_nest=1; +# +# USING INDEXES FOR ORDERING +# +# +# Using index scan on first table +# +set use_sort_nest=1; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey +ORDER BY o_orderDATE DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders index i_o_custkey i_o_orderdate 4 NULL 5 Using where +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey +ORDER BY o_orderDATE DESC +LIMIT 5; +c_nationkey c_name o_orderDate o_totalprice c_acctbal +16 Customer#000000088 1998-08-02 131752.07 8031.44 +0 Customer#000000080 1998-07-30 141858.97 7383.53 +10 Customer#000000049 1998-07-29 37776.79 4573.94 +3 Customer#000000022 1998-07-28 139104.17 591.98 +3 Customer#000000022 1998-07-27 82746.74 591.98 +set use_sort_nest=0; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey +ORDER BY o_orderDATE DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders index i_o_custkey i_o_orderdate 4 NULL 5 Using where +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey +ORDER BY o_orderDATE DESC +LIMIT 5; +c_nationkey c_name o_orderDate o_totalprice c_acctbal +16 Customer#000000088 1998-08-02 131752.07 8031.44 +0 Customer#000000080 1998-07-30 141858.97 7383.53 +10 Customer#000000049 1998-07-29 37776.79 4573.94 +3 Customer#000000022 1998-07-28 139104.17 591.98 +3 Customer#000000022 1998-07-27 82746.74 591.98 +# +# Using range access for customer with index on (c), this does +# the ordering +# +alter table orders add key o_orderdate(o_orderDATE, o_custkey); +set use_sort_nest=1; +explain SELECT +o_custkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey AND +o_orderDATE >='1997-07-30' and o_orderDATE <= '1998-07-30' +ORDER BY o_orderDATE, o_custkey DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey,o_orderdate i_o_orderdate 4 NULL 217 Using index condition; Using where; Using filesort +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +o_custkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey AND +o_orderDATE >='1997-07-30' and o_orderDATE <= '1998-07-30' +ORDER BY o_orderDATE, o_custkey DESC +LIMIT 5; +o_custkey c_name o_orderDate o_totalprice c_acctbal +130 Customer#000000130 1997-07-30 67979.49 5073.58 +101 Customer#000000101 1997-07-31 79189.58 7470.96 +13 Customer#000000013 1997-07-31 125188.72 3857.34 +64 Customer#000000064 1997-08-03 76164.41 -646.64 +50 Customer#000000050 1997-08-03 20791.5 4266.13 +set use_sort_nest=0; +explain SELECT +o_custkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey AND +o_orderDATE >='1997-07-30' and o_orderDATE <= '1998-07-30' +ORDER BY o_orderDATE, o_custkey DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey,o_orderdate i_o_orderdate 4 NULL 217 Using index condition; Using where; Using filesort +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +o_custkey, c_name, o_orderDate, o_totalprice, c_acctbal +FROM +orders, customer +WHERE +c_custkey= o_custkey AND +o_orderDATE >='1997-07-30' and o_orderDATE <= '1998-07-30' +ORDER BY o_orderDATE, o_custkey DESC +LIMIT 5; +o_custkey c_name o_orderDate o_totalprice c_acctbal +130 Customer#000000130 1997-07-30 67979.49 5073.58 +101 Customer#000000101 1997-07-31 79189.58 7470.96 +13 Customer#000000013 1997-07-31 125188.72 3857.34 +64 Customer#000000064 1997-08-03 76164.41 -646.64 +50 Customer#000000050 1997-08-03 20791.5 4266.13 +alter table orders drop key o_orderDate; +# +# USING FILESORT +# +# +# Filesort on first table (orders) +# +set use_sort_nest=1; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-01-30' and o_orderDATE <= '1997-12-31' +ORDER BY o_totalprice DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey i_o_orderdate 4 NULL 201 Using index condition; Using where; Using filesort +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 Using where +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 dbt3.customer.c_nationkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-01-30' and o_orderDATE <= '1997-12-31' +ORDER BY o_totalprice DESC +LIMIT 10; +c_nationkey c_name o_orderDate o_totalprice c_acctbal n_name +5 Customer#000000010 1997-04-04 258779.02 2753.54 ETHIOPIA +0 Customer#000000076 1997-08-24 231831.35 5745.33 ALGERIA +9 Customer#000000094 1997-10-17 224382.57 5500.11 INDONESIA +6 Customer#000000046 1997-05-26 216826.73 5744.59 FRANCE +3 Customer#000000005 1997-04-23 210643.96 794.47 CANADA +11 Customer#000000148 1997-07-25 206179.68 2135.6 IRAQ +2 Customer#000000101 1997-07-28 204163.1 7470.96 BRAZIL +3 Customer#000000005 1997-11-09 190142.17 794.47 CANADA +9 Customer#000000094 1997-11-30 187516.29 5500.11 INDONESIA +3 Customer#000000005 1997-11-12 185496.66 794.47 CANADA +set use_sort_nest=0; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-01-30' and o_orderDATE <= '1997-12-31' +ORDER BY o_totalprice DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey i_o_orderdate 4 NULL 201 Using index condition; Using where; Using filesort +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 Using where +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 dbt3.customer.c_nationkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-01-30' and o_orderDATE <= '1997-12-31' +ORDER BY o_totalprice DESC +LIMIT 10; +c_nationkey c_name o_orderDate o_totalprice c_acctbal n_name +5 Customer#000000010 1997-04-04 258779.02 2753.54 ETHIOPIA +0 Customer#000000076 1997-08-24 231831.35 5745.33 ALGERIA +9 Customer#000000094 1997-10-17 224382.57 5500.11 INDONESIA +6 Customer#000000046 1997-05-26 216826.73 5744.59 FRANCE +3 Customer#000000005 1997-04-23 210643.96 794.47 CANADA +11 Customer#000000148 1997-07-25 206179.68 2135.6 IRAQ +2 Customer#000000101 1997-07-28 204163.1 7470.96 BRAZIL +3 Customer#000000005 1997-11-09 190142.17 794.47 CANADA +9 Customer#000000094 1997-11-30 187516.29 5500.11 INDONESIA +3 Customer#000000005 1997-11-12 185496.66 794.47 CANADA +# +# Filesort on first table (lineitem) +# +show create table orders; +Table Create Table +orders CREATE TABLE `orders` ( + `o_orderkey` int(11) NOT NULL, + `o_custkey` int(11) DEFAULT NULL, + `o_orderstatus` char(1) DEFAULT NULL, + `o_totalprice` double DEFAULT NULL, + `o_orderDATE` date DEFAULT NULL, + `o_orderpriority` char(15) DEFAULT NULL, + `o_clerk` char(15) DEFAULT NULL, + `o_shippriority` int(11) DEFAULT NULL, + `o_comment` varchar(79) DEFAULT NULL, + PRIMARY KEY (`o_orderkey`), + KEY `i_o_orderdate` (`o_orderDATE`), + KEY `i_o_custkey` (`o_custkey`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 +set use_sort_nest=1; +explain SELECT +l_extendedprice, o_orderkey, o_totalprice, l_shipDATE +FROM +orders, lineitem +WHERE +o_orderkey = l_orderkey AND +l_shipDATE >= '1996-01-01' AND l_shipDATE <= '1996-12-31' + ORDER BY l_extendedprice DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE lineitem range PRIMARY,i_l_shipdate,i_l_orderkey,i_l_orderkey_quantity i_l_shipdate 4 NULL 910 Using index condition; Using filesort +1 SIMPLE orders eq_ref PRIMARY PRIMARY 4 dbt3.lineitem.l_orderkey 1 +SELECT +l_extendedprice, o_orderkey, o_totalprice, l_shipDATE +FROM +orders, lineitem +WHERE +o_orderkey = l_orderkey AND +l_shipDATE >= '1996-01-01' AND l_shipDATE <= '1996-12-31' + ORDER BY l_extendedprice DESC +LIMIT 10; +l_extendedprice o_orderkey o_totalprice l_shipDATE +54559.5 1574 179923.54 1996-12-14 +53508.5 2342 104038.78 1996-08-28 +53458 1153 220727.97 1996-06-27 +53075.82 2721 59180.25 1996-02-14 +52830.33 1284 106122.38 1996-04-11 +52657.5 2276 141159.63 1996-07-13 +52290.84 4773 196080.26 1996-01-26 +51752.16 1607 166335.03 1996-02-22 +51751.35 1700 89143.36 1996-09-26 +51709.4 1254 94649.25 1996-03-07 +set use_sort_nest=0; +explain SELECT +l_extendedprice, o_orderkey, o_totalprice, l_shipDATE +FROM +orders, lineitem +WHERE +o_orderkey = l_orderkey AND +l_shipDATE >= '1996-01-01' AND l_shipDATE <= '1996-12-31' + ORDER BY l_extendedprice DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE lineitem range PRIMARY,i_l_shipdate,i_l_orderkey,i_l_orderkey_quantity i_l_shipdate 4 NULL 910 Using index condition; Using filesort +1 SIMPLE orders eq_ref PRIMARY PRIMARY 4 dbt3.lineitem.l_orderkey 1 +SELECT +l_extendedprice, o_orderkey, o_totalprice, l_shipDATE +FROM +orders, lineitem +WHERE +o_orderkey = l_orderkey AND +l_shipDATE >= '1996-01-01' AND l_shipDATE <= '1996-12-31' + ORDER BY l_extendedprice DESC +LIMIT 10; +l_extendedprice o_orderkey o_totalprice l_shipDATE +54559.5 1574 179923.54 1996-12-14 +53508.5 2342 104038.78 1996-08-28 +53458 1153 220727.97 1996-06-27 +53075.82 2721 59180.25 1996-02-14 +52830.33 1284 106122.38 1996-04-11 +52657.5 2276 141159.63 1996-07-13 +52290.84 4773 196080.26 1996-01-26 +51752.16 1607 166335.03 1996-02-22 +51751.35 1700 89143.36 1996-09-26 +51709.4 1254 94649.25 1996-03-07 +# +# Using Filesort with Sort Nest +# +# +# FILESORT USING SORT-NEST on (orders, customer) +# +set use_sort_nest=1; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-07-01' and o_orderDATE <= '1997-07-31' +ORDER BY o_orderDATE DESC, c_acctbal DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey i_o_orderdate 4 NULL 20 Using index condition; Using where +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 10 Using filesort +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 sort-nest.c_nationkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-07-01' and o_orderDATE <= '1997-07-31' +ORDER BY o_orderDATE DESC, c_acctbal DESC +LIMIT 10; +c_nationkey c_name o_orderDate o_totalprice c_acctbal n_name +2 Customer#000000101 1997-07-31 79189.58 7470.96 BRAZIL +3 Customer#000000013 1997-07-31 125188.72 3857.34 CANADA +9 Customer#000000130 1997-07-30 67979.49 5073.58 INDONESIA +11 Customer#000000148 1997-07-29 88448.24 2135.6 IRAQ +2 Customer#000000101 1997-07-28 204163.1 7470.96 BRAZIL +11 Customer#000000052 1997-07-28 46393.97 5630.28 IRAQ +4 Customer#000000004 1997-07-25 11405.4 2866.83 EGYPT +11 Customer#000000148 1997-07-25 206179.68 2135.6 IRAQ +18 Customer#000000082 1997-07-20 15082.82 9468.34 CHINA +10 Customer#000000055 1997-07-16 46380.69 4572.11 IRAN +set use_sort_nest=0; +explain SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-07-01' and o_orderDATE <= '1997-07-31' +ORDER BY o_orderDATE DESC, c_acctbal DESC +LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range i_o_orderdate,i_o_custkey i_o_orderdate 4 NULL 20 Using index condition; Using where; Using temporary; Using filesort +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 Using where +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 dbt3.customer.c_nationkey 1 +SELECT +c_nationkey, c_name, o_orderDate, o_totalprice, c_acctbal, n_name +FROM +orders, customer, nation +WHERE +c_custkey= o_custkey AND c_nationkey=n_nationkey AND +o_orderDATE >='1997-07-01' and o_orderDATE <= '1997-07-31' +ORDER BY o_orderDATE DESC, c_acctbal DESC +LIMIT 10; +c_nationkey c_name o_orderDate o_totalprice c_acctbal n_name +2 Customer#000000101 1997-07-31 79189.58 7470.96 BRAZIL +3 Customer#000000013 1997-07-31 125188.72 3857.34 CANADA +9 Customer#000000130 1997-07-30 67979.49 5073.58 INDONESIA +11 Customer#000000148 1997-07-29 88448.24 2135.6 IRAQ +2 Customer#000000101 1997-07-28 204163.1 7470.96 BRAZIL +11 Customer#000000052 1997-07-28 46393.97 5630.28 IRAQ +4 Customer#000000004 1997-07-25 11405.4 2866.83 EGYPT +11 Customer#000000148 1997-07-25 206179.68 2135.6 IRAQ +18 Customer#000000082 1997-07-20 15082.82 9468.34 CHINA +10 Customer#000000055 1997-07-16 46380.69 4572.11 IRAN +# +# Sort-nest on table (lineitem, customer) +# +set use_sort_nest=1; +explain SELECT +c_acctbal, c_name, o_orderDate, l_extendedprice, l_orderkey +FROM +orders, customer, lineitem +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +l_orderkey between 5500 AND 6000 +AND +l_shipDATE >= '1997-01-01' and l_shipDATE <= '1998-08-10' + ORDER BY l_extendedprice DESC, c_acctbal DESC +LIMIT 20; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range PRIMARY,i_o_custkey PRIMARY 4 NULL 125 Using where; Using temporary; Using filesort +1 SIMPLE lineitem ref PRIMARY,i_l_shipdate,i_l_orderkey,i_l_orderkey_quantity PRIMARY 4 dbt3.orders.o_orderkey 4 Using where +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +SELECT +c_acctbal, c_name, o_orderDate, l_extendedprice, l_orderkey +FROM +orders, customer, lineitem +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +l_orderkey between 5500 AND 6000 +AND +l_shipDATE >= '1997-01-01' and l_shipDATE <= '1998-08-10' + ORDER BY l_extendedprice DESC, c_acctbal DESC +LIMIT 20; +c_acctbal c_name o_orderDate l_extendedprice l_orderkey +1842.49 Customer#000000124 1997-11-06 54759.5 5857 +2135.6 Customer#000000148 1997-04-14 53909.8 5952 +4681.03 Customer#000000016 1998-07-06 53811.31 5761 +794.47 Customer#000000005 1997-04-23 53758.5 5859 +-234.12 Customer#000000125 1997-01-11 53468.31 5829 +5121.28 Customer#000000079 1998-05-31 53208 5633 +5744.59 Customer#000000046 1998-04-14 50770.37 5604 +-917.75 Customer#000000037 1997-07-13 50310.72 5793 +121.65 Customer#000000002 1998-05-28 49830.24 5507 +121.65 Customer#000000002 1998-05-28 49542.24 5507 +7470.96 Customer#000000101 1997-05-27 49411.82 5923 +5327.38 Customer#000000095 1997-10-04 48859.36 5505 +1842.49 Customer#000000124 1997-11-06 48661.41 5857 +4573.94 Customer#000000049 1997-02-14 48557.11 5762 +-646.64 Customer#000000064 1997-01-01 48219.92 5895 +-646.64 Customer#000000064 1997-01-01 48039.64 5895 +5121.28 Customer#000000079 1998-05-31 48004.8 5633 +9904.28 Customer#000000043 1998-02-06 47339.52 5671 +8959.65 Customer#000000149 1996-11-12 47247.52 5606 +5744.59 Customer#000000046 1998-04-14 45589.72 5604 +set use_sort_nest=0; +explain SELECT +c_acctbal, c_name, o_orderDate, l_extendedprice, l_orderkey +FROM +orders, customer, lineitem +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +l_orderkey between 5500 AND 6000 +AND +l_shipDATE >= '1997-01-01' and l_shipDATE <= '1998-08-10' + ORDER BY l_extendedprice DESC, c_acctbal DESC +LIMIT 20; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range PRIMARY,i_o_custkey PRIMARY 4 NULL 125 Using where; Using temporary; Using filesort +1 SIMPLE customer eq_ref PRIMARY PRIMARY 4 dbt3.orders.o_custkey 1 +1 SIMPLE lineitem ref PRIMARY,i_l_shipdate,i_l_orderkey,i_l_orderkey_quantity PRIMARY 4 dbt3.orders.o_orderkey 4 Using where +SELECT +c_acctbal, c_name, o_orderDate, l_extendedprice, l_orderkey +FROM +orders, customer, lineitem +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +l_orderkey between 5500 AND 6000 +AND +l_shipDATE >= '1997-01-01' and l_shipDATE <= '1998-08-10' + ORDER BY l_extendedprice DESC, c_acctbal DESC +LIMIT 20; +c_acctbal c_name o_orderDate l_extendedprice l_orderkey +1842.49 Customer#000000124 1997-11-06 54759.5 5857 +2135.6 Customer#000000148 1997-04-14 53909.8 5952 +4681.03 Customer#000000016 1998-07-06 53811.31 5761 +794.47 Customer#000000005 1997-04-23 53758.5 5859 +-234.12 Customer#000000125 1997-01-11 53468.31 5829 +5121.28 Customer#000000079 1998-05-31 53208 5633 +5744.59 Customer#000000046 1998-04-14 50770.37 5604 +-917.75 Customer#000000037 1997-07-13 50310.72 5793 +121.65 Customer#000000002 1998-05-28 49830.24 5507 +121.65 Customer#000000002 1998-05-28 49542.24 5507 +7470.96 Customer#000000101 1997-05-27 49411.82 5923 +5327.38 Customer#000000095 1997-10-04 48859.36 5505 +1842.49 Customer#000000124 1997-11-06 48661.41 5857 +4573.94 Customer#000000049 1997-02-14 48557.11 5762 +-646.64 Customer#000000064 1997-01-01 48219.92 5895 +-646.64 Customer#000000064 1997-01-01 48039.64 5895 +5121.28 Customer#000000079 1998-05-31 48004.8 5633 +9904.28 Customer#000000043 1998-02-06 47339.52 5671 +8959.65 Customer#000000149 1996-11-12 47247.52 5606 +5744.59 Customer#000000046 1998-04-14 45589.72 5604 +######################################################################## +# +# Sort-nest on table (customer, orders, lineitem) +# +set use_sort_nest=1; +explain SELECT +l_extendedprice, c_acctbal, o_totalprice, o_orderDate, l_orderkey +FROM +orders, customer, lineitem, nation +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +n_nationkey = c_nationkey +AND +l_orderkey between 5500 AND 6000 +AND +c_custkey between 1 and 10 +ORDER BY +l_extendedprice DESC, c_acctbal DESC, o_totalprice DESC +LIMIT 20; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range PRIMARY,i_o_custkey PRIMARY 4 NULL 125 Using where +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 +1 SIMPLE lineitem ref PRIMARY,i_l_orderkey,i_l_orderkey_quantity PRIMARY 4 dbt3.orders.o_orderkey 4 +1 SIMPLE <sort-nest> ALL NULL NULL NULL NULL 20 Using filesort +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 sort-nest.c_nationkey 1 Using index +SELECT +l_extendedprice, c_acctbal, o_totalprice, o_orderDate, l_orderkey +FROM +orders, customer, lineitem, nation +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +n_nationkey = c_nationkey +AND +l_orderkey between 5500 AND 6000 +AND +c_custkey between 1 and 10 +ORDER BY +l_extendedprice DESC, c_acctbal DESC, o_totalprice DESC +LIMIT 20; +l_extendedprice c_acctbal o_totalprice o_orderDate l_orderkey +53758.5 794.47 210643.96 1997-04-23 5859 +49830.24 121.65 140363.7 1998-05-28 5507 +49542.24 121.65 140363.7 1998-05-28 5507 +48745.11 6819.74 122823.78 1993-04-05 5794 +47992.64 6819.74 140838.11 1998-06-26 5763 +47615.98 6819.74 182966.39 1994-07-17 5572 +46705.74 9561.95 101429.61 1993-04-21 5670 +44467.59 121.65 44777.63 1992-07-08 5893 +44442.3 6819.74 122823.78 1993-04-05 5794 +39723.6 794.47 210643.96 1997-04-23 5859 +37048.32 9561.95 95312.81 1992-03-28 5953 +36860.25 794.47 210643.96 1997-04-23 5859 +32996.16 6819.74 140838.11 1998-06-26 5763 +31416.68 6819.74 182966.39 1994-07-17 5572 +31219.32 794.47 210643.96 1997-04-23 5859 +31042.34 9561.95 95312.81 1992-03-28 5953 +29462.13 794.47 210643.96 1997-04-23 5859 +28948.59 6819.74 182966.39 1994-07-17 5572 +26732.43 9561.95 101429.61 1993-04-21 5670 +24590.68 9561.95 95312.81 1992-03-28 5953 +set use_sort_nest=0; +explain SELECT +l_extendedprice, c_acctbal, o_totalprice, o_orderDate, l_orderkey +FROM +orders, customer, lineitem, nation +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +n_nationkey = c_nationkey +AND +l_orderkey between 5500 AND 6000 +AND +c_custkey between 1 and 10 +ORDER BY +l_extendedprice DESC, c_acctbal DESC, o_totalprice DESC +LIMIT 20; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE orders range PRIMARY,i_o_custkey PRIMARY 4 NULL 125 Using where; Using temporary; Using filesort +1 SIMPLE customer eq_ref PRIMARY,i_c_nationkey PRIMARY 4 dbt3.orders.o_custkey 1 Using where +1 SIMPLE nation eq_ref PRIMARY PRIMARY 4 dbt3.customer.c_nationkey 1 Using index +1 SIMPLE lineitem ref PRIMARY,i_l_orderkey,i_l_orderkey_quantity PRIMARY 4 dbt3.orders.o_orderkey 4 +SELECT +l_extendedprice, c_acctbal, o_totalprice, o_orderDate, l_orderkey +FROM +orders, customer, lineitem, nation +WHERE +c_custkey= o_custkey +AND +l_orderkey = o_orderkey +AND +n_nationkey = c_nationkey +AND +l_orderkey between 5500 AND 6000 +AND +c_custkey between 1 and 10 +ORDER BY +l_extendedprice DESC, c_acctbal DESC, o_totalprice DESC +LIMIT 20; +l_extendedprice c_acctbal o_totalprice o_orderDate l_orderkey +53758.5 794.47 210643.96 1997-04-23 5859 +49830.24 121.65 140363.7 1998-05-28 5507 +49542.24 121.65 140363.7 1998-05-28 5507 +48745.11 6819.74 122823.78 1993-04-05 5794 +47992.64 6819.74 140838.11 1998-06-26 5763 +47615.98 6819.74 182966.39 1994-07-17 5572 +46705.74 9561.95 101429.61 1993-04-21 5670 +44467.59 121.65 44777.63 1992-07-08 5893 +44442.3 6819.74 122823.78 1993-04-05 5794 +39723.6 794.47 210643.96 1997-04-23 5859 +37048.32 9561.95 95312.81 1992-03-28 5953 +36860.25 794.47 210643.96 1997-04-23 5859 +32996.16 6819.74 140838.11 1998-06-26 5763 +31416.68 6819.74 182966.39 1994-07-17 5572 +31219.32 794.47 210643.96 1997-04-23 5859 +31042.34 9561.95 95312.81 1992-03-28 5953 +29462.13 794.47 210643.96 1997-04-23 5859 +28948.59 6819.74 182966.39 1994-07-17 5572 +26732.43 9561.95 101429.61 1993-04-21 5670 +24590.68 9561.95 95312.81 1992-03-28 5953 +drop database dbt3; diff --git a/mysql-test/main/sort_nest_dbt3_innodb.test b/mysql-test/main/sort_nest_dbt3_innodb.test new file mode 100644 index 00000000000..d72329eaab6 --- /dev/null +++ b/mysql-test/main/sort_nest_dbt3_innodb.test @@ -0,0 +1,6 @@ +--source include/have_innodb.inc + +SET SESSION STORAGE_ENGINE='InnoDB'; + +--source sort_nest_dbt3.test + diff --git a/mysql-test/main/sort_nest_index.result b/mysql-test/main/sort_nest_index.result new file mode 100644 index 00000000000..7dae6c6c93f --- /dev/null +++ b/mysql-test/main/sort_nest_index.result @@ -0,0 +1,342 @@ +CREATE TABLE t1 (a int, b int, c int, KEY a_b (a,b), KEY a_c (a,c)); +insert into t1 values (0,1,0), (0,2,0), (0,3,0), (0,4,0), (0,5,0), (0,6,0); +insert into t1 values (1,7,1), (1,8,1), (1,9,1), (1,10,1), (1,11,1), (1,12,1); +insert into t1 values (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +insert into t1 values (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +# +# index a_b should be used, no need for filesort +# +set use_sort_nest= 1; +select a,b,c from t1 where a=1 and c=2 order by b limit 5; +a b c +1 7 2 +1 7 2 +1 8 2 +1 8 2 +1 9 2 +explain select a,b,c from t1 where a=1 and c=2 order by b limit 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a_b,a_c a_b 5 NULL 10 Using index condition; Using where +set use_sort_nest= 0; +explain select a,b,c from t1 where a=1 and c=2 order by b limit 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a_b,a_c a_b 5 NULL 18 Using where +drop table t1; +# +# Tests where Index(scan, ref or range access) satisfies the ORDERING +# +CREATE TABLE t1 (a int, b int, c int, KEY a_b (a,b), KEY a_c (a,c)); +insert into t1 values (0,1,0), (0,2,0), (0,3,0), (0,4,0), (0,5,0), (0,6,0); +insert into t1 values (1,7,1), (1,8,1), (1,9,1), (1,10,1), (1,11,1), (1,12,1); +insert into t1 values (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +insert into t1 values (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +insert into t1 values (1,1,2); +# index key a_b, no need for filesort +set optimizer_trace=1; +set use_sort_nest=1; +select a,b,c from t1 where a=1 and c=2 order by b limit 10; +a b c +1 1 2 +1 7 2 +1 7 2 +1 8 2 +1 8 2 +1 9 2 +1 9 2 +1 10 2 +1 10 2 +1 11 2 +explain select a,b,c from t1 where a=1 and c=2 order by b limit 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a_b,a_c a_b 5 NULL 19 Using index condition; Using where +set use_sort_nest=0; +explain select a,b,c from t1 where a=1 and c=2 order by b limit 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a_b,a_c a_b 5 NULL 19 Using where +drop table t1; +CREATE TABLE t1( +a int NOT NULL, +b char NULL, +PRIMARY KEY(a) +); +INSERT INTO t1 VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'); +# +# Should use index condition +# +set use_sort_nest= 1; +SELECT * FROM t1 +WHERE a BETWEEN 1 and 2 +ORDER BY a +LIMIT 2; +a b +1 a +2 b +EXPLAIN SELECT * FROM t1 +WHERE a BETWEEN 1 and 2 +ORDER BY a +LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 2 Using index condition +set use_sort_nest= 0; +EXPLAIN SELECT * FROM t1 +WHERE a BETWEEN 1 and 2 +ORDER BY a +LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 2 Using index condition +# +# Should not use index condition as ORDER by DESC is used +# +set use_sort_nest= 1; +EXPLAIN SELECT * FROM t1 +WHERE a BETWEEN 1 and 2 +ORDER BY a DESC +LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 2 Using where +SELECT * FROM t1 +WHERE a BETWEEN 1 and 2 +ORDER BY a DESC +LIMIT 2; +a b +2 b +1 a +set use_sort_nest= 0; +EXPLAIN SELECT * FROM t1 +WHERE a BETWEEN 1 and 2 +ORDER BY a DESC +LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 2 Using where +drop table t1; +create table t1(a int, b int, c int, key(a), key a_b(a,b)); +insert into t1 values (0,1,0), (0,2,0), (0,3,0); +insert into t1 values (1,6,1), (1,7,1), (1,5,1); +insert into t1 values (2,8,2), (2,9,3), (2,10,4); +insert into t1 values (3,1,5); +create table t2(a int, b int, c int, key(b), key(c)); +insert into t2 select a, b, c from t1; +# +# Testing using of Indexes on first non-const table +# +# +# Using range scan +# +set use_sort_nest= 1; +SELECT * +FROM +t1,t2 +WHERE +t1.a=2 AND t2.b > 8 AND +t1.b=t2.b +ORDER BY t1.b LIMIT 10; +a b c a b c +2 9 3 2 9 3 +2 10 4 2 10 4 +EXPLAIN SELECT * +FROM +t1,t2 +WHERE +t1.a=2 AND t2.b > 8 AND +t1.b=t2.b +ORDER BY t1.b LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a,a_b a_b 10 NULL 2 Using index condition +1 SIMPLE t2 ref b b 5 test.t1.b 1 +set use_sort_nest= 0; +EXPLAIN SELECT * +FROM +t1,t2 +WHERE +t1.a=2 AND t2.b > 8 AND +t1.b=t2.b +ORDER BY t1.b LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a,a_b a_b 10 NULL 2 Using index condition +1 SIMPLE t2 ref b b 5 test.t1.b 1 +# +# Using ref access +# +set use_sort_nest= 1; +SELECT * +FROM +t1,t2 +WHERE +t1.a=2 AND t2.c >= 1 AND +t1.b=t2.b +ORDER BY t1.b LIMIT 10; +a b c a b c +2 8 2 2 8 2 +2 9 3 2 9 3 +2 10 4 2 10 4 +EXPLAIN SELECT * +FROM +t1,t2 +WHERE +t1.a=2 AND t2.c >= 1 AND +t1.b=t2.b +ORDER BY t1.b LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ref a,a_b a_b 5 const 3 Using index condition; Using where +1 SIMPLE t2 ref b,c b 5 test.t1.b 1 Using where +set use_sort_nest= 0; +EXPLAIN SELECT * +FROM +t1,t2 +WHERE +t1.a=2 AND t2.c >= 1 AND +t1.b=t2.b +ORDER BY t1.b LIMIT 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ref a,a_b a_b 5 const 3 Using where +1 SIMPLE t2 ref b,c b 5 test.t1.b 1 Using where +drop table t1,t2; +# TESTS with INDEX HINTS +set use_sort_nest=1; +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t1 (a int, b int,c int, key idx1(a), key idx2(a,b), key idx3(c)); +insert into t1 select a,a,a from t0 where a <5; +analyze table t1 persistent for all; +# +# Index idx1 to be used for index scan +# +set use_sort_nest=1; +SELECT * from t1 where b > 0 order by t1.a limit 2; +a b c +1 1 1 +2 2 2 +EXPLAIN SELECT * from t1 where b > 0 order by t1.a limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx1 5 NULL 2 Using where +set use_sort_nest=0; +EXPLAIN SELECT * from t1 where b > 0 order by t1.a limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx1 5 NULL 2 Using where +# +# Index idx2 to be used for index scan(USE INDEX is used) +# +set use_sort_nest=1; +SELECT * from t1 USE INDEX(idx2) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +a b c +1 1 1 +2 2 2 +EXPLAIN SELECT * from t1 USE INDEX(idx2) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx2 10 NULL 2 Using where +set use_sort_nest=0; +EXPLAIN SELECT * from t1 USE INDEX(idx2) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx2 10 NULL 2 Using where +# +# Index idx2 to be used for index scan(USE INDEX for ORDER BY is used) +# +set use_sort_nest=1; +SELECT * from t1 USE INDEX FOR ORDER BY(idx2) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +a b c +1 1 1 +2 2 2 +EXPLAIN SELECT * from t1 USE INDEX FOR ORDER BY(idx2) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx2 10 NULL 2 Using where +set use_sort_nest=0; +EXPLAIN SELECT * from t1 USE INDEX FOR ORDER BY(idx2) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx2 10 NULL 2 Using where +# +# Use Filesort as idx3 does not resolve ORDER BY clause +# +set use_sort_nest=1; +SELECT * from t1 USE INDEX FOR ORDER BY(idx3) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +a b c +1 1 1 +2 2 2 +EXPLAIN SELECT * from t1 USE INDEX FOR ORDER BY(idx3) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 Using where; Using filesort +set use_sort_nest=0; +EXPLAIN SELECT * from t1 USE INDEX FOR ORDER BY(idx3) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 Using where; Using filesort +# +# Using index idx2 as idx1 is ignored +# +set use_sort_nest=1; +SELECT * from t1 IGNORE INDEX(idx1) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +a b c +1 1 1 +2 2 2 +EXPLAIN SELECT * from t1 IGNORE INDEX(idx1) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx2 10 NULL 2 Using where +set use_sort_nest=0; +EXPLAIN SELECT * from t1 IGNORE INDEX(idx1) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx2 10 NULL 2 Using where +# +# Use index idx2 for sorting, it is forced here +# +set use_sort_nest=1; +SELECT * from t1 FORCE INDEX(idx2) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +a b c +1 1 1 +2 2 2 +EXPLAIN SELECT * from t1 FORCE INDEX(idx2) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx2 10 NULL 2 Using where +set use_sort_nest=0; +EXPLAIN SELECT * from t1 FORCE INDEX(idx2) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL idx2 10 NULL 2 Using where +# +# Use FILESORT as idx3 cannot resolve ORDER BY clause +# +set use_sort_nest=1; +SELECT * from t1 FORCE INDEX FOR ORDER BY(idx3) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +a b c +1 1 1 +2 2 2 +EXPLAIN SELECT * from t1 FORCE INDEX FOR ORDER BY(idx3) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 Using where; Using filesort +set use_sort_nest=0; +EXPLAIN SELECT * from t1 FORCE INDEX FOR ORDER BY(idx3) +WHERE b > 0 +ORDER BY t1.a LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 Using where; Using filesort +drop table t0,t1; diff --git a/mysql-test/main/sort_nest_index.test b/mysql-test/main/sort_nest_index.test new file mode 100644 index 00000000000..08856fc1723 --- /dev/null +++ b/mysql-test/main/sort_nest_index.test @@ -0,0 +1,249 @@ +CREATE TABLE t1 (a int, b int, c int, KEY a_b (a,b), KEY a_c (a,c)); + +insert into t1 values (0,1,0), (0,2,0), (0,3,0), (0,4,0), (0,5,0), (0,6,0); +insert into t1 values (1,7,1), (1,8,1), (1,9,1), (1,10,1), (1,11,1), (1,12,1); +insert into t1 values (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +insert into t1 values (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); + +--echo # +--echo # index a_b should be used, no need for filesort +--echo # + +let $query= select a,b,c from t1 where a=1 and c=2 order by b limit 5; +set use_sort_nest= 1; +eval $query; +eval explain $query; + +set use_sort_nest= 0; +eval explain $query; +drop table t1; + +--echo # +--echo # Tests where Index(scan, ref or range access) satisfies the ORDERING +--echo # + +CREATE TABLE t1 (a int, b int, c int, KEY a_b (a,b), KEY a_c (a,c)); + +insert into t1 values (0,1,0), (0,2,0), (0,3,0), (0,4,0), (0,5,0), (0,6,0); +insert into t1 values (1,7,1), (1,8,1), (1,9,1), (1,10,1), (1,11,1), (1,12,1); +insert into t1 values (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +insert into t1 values (1,7,2), (1,8,2), (1,9,2), (1,10,2), (1,11,2), (1,12,2); +insert into t1 values (1,1,2); + +--echo # index key a_b, no need for filesort + +let $query= select a,b,c from t1 where a=1 and c=2 order by b limit 10; +set optimizer_trace=1; + +set use_sort_nest=1; +eval $query; +eval explain $query; + +set use_sort_nest=0; +eval explain $query; + +drop table t1; + +CREATE TABLE t1( + a int NOT NULL, + b char NULL, + PRIMARY KEY(a) +); + +INSERT INTO t1 VALUES (1,'a'), (2,'b'), (3,'c'), (4,'d'); + +--echo # +--echo # Should use index condition +--echo # + +let $query= SELECT * FROM t1 + WHERE a BETWEEN 1 and 2 + ORDER BY a + LIMIT 2; + +set use_sort_nest= 1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest= 0; +eval EXPLAIN $query; + +--echo # +--echo # Should not use index condition as ORDER by DESC is used +--echo # + +let $query= SELECT * FROM t1 + WHERE a BETWEEN 1 and 2 + ORDER BY a DESC + LIMIT 2; + +set use_sort_nest= 1; +eval EXPLAIN $query; +eval $query; + +set use_sort_nest= 0; +eval EXPLAIN $query; + +drop table t1; + +create table t1(a int, b int, c int, key(a), key a_b(a,b)); # 10 rows +insert into t1 values (0,1,0), (0,2,0), (0,3,0); +insert into t1 values (1,6,1), (1,7,1), (1,5,1); +insert into t1 values (2,8,2), (2,9,3), (2,10,4); +insert into t1 values (3,1,5); + +create table t2(a int, b int, c int, key(b), key(c)); # 10 rows +insert into t2 select a, b, c from t1; + +--echo # +--echo # Testing using of Indexes on first non-const table +--echo # + +--echo # +--echo # Using range scan +--echo # +let $query= SELECT * + FROM + t1,t2 + WHERE + t1.a=2 AND t2.b > 8 AND + t1.b=t2.b + ORDER BY t1.b LIMIT 10; + +set use_sort_nest= 1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest= 0; +eval EXPLAIN $query; + +--echo # +--echo # Using ref access +--echo # +let $query= SELECT * + FROM + t1,t2 + WHERE + t1.a=2 AND t2.c >= 1 AND + t1.b=t2.b + ORDER BY t1.b LIMIT 10; + +set use_sort_nest= 1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest= 0; +eval EXPLAIN $query; + +drop table t1,t2; + +--echo # TESTS with INDEX HINTS + +set use_sort_nest=1; +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t1 (a int, b int,c int, key idx1(a), key idx2(a,b), key idx3(c)); +insert into t1 select a,a,a from t0 where a <5; # 5 rows + +--disable_result_log +analyze table t1 persistent for all; +--enable_result_log + +--echo # +--echo # Index idx1 to be used for index scan +--echo # + +let $query= SELECT * from t1 where b > 0 order by t1.a limit 2; +set use_sort_nest=1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest=0; +eval EXPLAIN $query; + +--echo # +--echo # Index idx2 to be used for index scan(USE INDEX is used) +--echo # + +let $query= SELECT * from t1 USE INDEX(idx2) + WHERE b > 0 + ORDER BY t1.a LIMIT 2; +set use_sort_nest=1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest=0; +eval EXPLAIN $query; + +--echo # +--echo # Index idx2 to be used for index scan(USE INDEX for ORDER BY is used) +--echo # + +let $query= SELECT * from t1 USE INDEX FOR ORDER BY(idx2) + WHERE b > 0 + ORDER BY t1.a LIMIT 2; +set use_sort_nest=1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest=0; +eval EXPLAIN $query; + +--echo # +--echo # Use Filesort as idx3 does not resolve ORDER BY clause +--echo # + +let $query= SELECT * from t1 USE INDEX FOR ORDER BY(idx3) + WHERE b > 0 + ORDER BY t1.a LIMIT 2; +set use_sort_nest=1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest=0; +eval EXPLAIN $query; + +--echo # +--echo # Using index idx2 as idx1 is ignored +--echo # + +let $query= SELECT * from t1 IGNORE INDEX(idx1) + WHERE b > 0 + ORDER BY t1.a LIMIT 2; +set use_sort_nest=1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest=0; +eval EXPLAIN $query; + +--echo # +--echo # Use index idx2 for sorting, it is forced here +--echo # + +let $query= SELECT * from t1 FORCE INDEX(idx2) + WHERE b > 0 + ORDER BY t1.a LIMIT 2; +set use_sort_nest=1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest=0; +eval EXPLAIN $query; + +--echo # +--echo # Use FILESORT as idx3 cannot resolve ORDER BY clause +--echo # + +let $query= SELECT * from t1 FORCE INDEX FOR ORDER BY(idx3) + WHERE b > 0 + ORDER BY t1.a LIMIT 2; + +set use_sort_nest=1; +eval $query; +eval EXPLAIN $query; + +set use_sort_nest=0; +eval EXPLAIN $query; + +drop table t0,t1; diff --git a/mysql-test/main/sort_nest_sj.result b/mysql-test/main/sort_nest_sj.result new file mode 100644 index 00000000000..73405cd8832 --- /dev/null +++ b/mysql-test/main/sort_nest_sj.result @@ -0,0 +1,697 @@ +# +# SORT-NEST WITH SEMI JOINS +# + +# MERGED SEMI-JOINS + +# SEMI JOIN MATERIALIZATION SCAN with SORT-NEST +CREATE TABLE t0(a int); +CREATE TABLE t1 (a int, b int, c int); +CREATE TABLE t2 (a int, b int, c int); +CREATE TABLE t3 (a int, b int, c int, key(a)); +CREATE TABLE t4 (a int, b int, c int, key(a)); +INSERT INTO t0 SELECT seq-1 FROM seq_1_to_10; +INSERT INTO t1 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_100; +INSERT INTO t2 SELECT a,a,a FROM t0; +INSERT INTO t3 SELECT a,a,a FROM t0; +INSERT INTO t4 SELECT a,a,a FROM t0; +ANALYZE TABLE t0 PERSISTENT FOR ALL; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +ANALYZE TABLE t4 PERSISTENT FOR ALL; +# SJM scan inside the sort-nest +# sort-nest includes (t2, <subquery2>) +set use_sort_nest=1; +EXPLAIN SELECT t1.a, t2.a, t1.b,t2.b +FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT t3.b FROM t3,t4 +WHERE t3.a < 3 AND t3.a=t4.a) +ORDER BY t1.b DESC ,t2.b DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 10 +1 PRIMARY <subquery2> ALL distinct_key NULL NULL NULL 3 +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 PRIMARY t1 ALL NULL NULL NULL NULL 100 Using where +2 MATERIALIZED t4 range a a 5 NULL 3 Using where; Using index +2 MATERIALIZED t3 ref a a 5 test.t4.a 1 +EXPLAIN FORMAT=JSON SELECT t1.a, t2.a, t1.b,t2.b +FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT t3.b FROM t3,t4 +WHERE t3.a < 3 AND t3.a=t4.a) +ORDER BY t1.b DESC ,t2.b DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "table": { + "table_name": "<subquery2>", + "access_type": "ALL", + "possible_keys": ["distinct_key"], + "rows": 3, + "filtered": 100, + "materialized": { + "unique": 1, + "query_block": { + "select_id": 2, + "table": { + "table_name": "t4", + "access_type": "range", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "rows": 3, + "filtered": 100, + "attached_condition": "t4.a < 3 and t4.a is not null", + "using_index": true + }, + "table": { + "table_name": "t3", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["test.t4.a"], + "rows": 1, + "filtered": 100 + } + } + } + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b desc, `sort-nest`.b desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 100, + "filtered": 100, + "attached_condition": "t1.a = `sort-nest`.a and t1.b = `sort-nest`.b" + } + } +} +SELECT t1.a, t2.a, t1.b,t2.b +FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT t3.b FROM t3,t4 +WHERE t3.a < 3 AND t3.a=t4.a) +ORDER BY t1.b DESC ,t2.b DESC +LIMIT 5; +a a b b +2 2 2 2 +1 1 1 1 +0 0 0 0 +set use_sort_nest=0; +SELECT t1.a, t2.a, t1.b,t2.b +FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT t3.b FROM t3,t4 +WHERE t3.a < 3 AND t3.a=t4.a) +ORDER BY t1.b DESC ,t2.b DESC +LIMIT 5; +a a b b +2 2 2 2 +1 1 1 1 +0 0 0 0 +# +# SJM scan table is the first table inside the sort-nest +# +alter table t2 add key(b); +set use_sort_nest=1; +EXPLAIN SELECT t1.a, t2.a, t1.b,t2.b +FROM t1, t2 +WHERE t1.a=t2.a AND t2.b < 5 AND +t1.b IN (SELECT t3.b FROM t3,t4 +WHERE t3.a < 3 AND t3.a=t4.a) +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY <subquery2> ALL distinct_key NULL NULL NULL 3 +1 PRIMARY t2 ALL b NULL NULL NULL 10 Using where; Using join buffer (flat, BNL join) +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 PRIMARY t1 ALL NULL NULL NULL NULL 100 Using where +2 MATERIALIZED t4 range a a 5 NULL 3 Using where; Using index +2 MATERIALIZED t3 ref a a 5 test.t4.a 1 +SELECT t1.a, t2.a, t1.b,t2.b +FROM t1, t2 +WHERE t1.a=t2.a AND t2.b < 5 AND +t1.b IN (SELECT t3.b FROM t3,t4 +WHERE t3.a < 3 AND t3.a=t4.a) +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5; +a a b b +2 2 2 2 +1 1 1 1 +0 0 0 0 +set use_sort_nest= 0; +SELECT t1.a, t2.a, t1.b,t2.b +FROM t1, t2 +WHERE t1.a=t2.a AND t2.b < 5 AND +t1.b IN (SELECT t3.b FROM t3,t4 +WHERE t3.a < 3 AND t3.a=t4.a) +ORDER BY t2.b DESC, t1.b DESC +LIMIT 5; +a a b b +2 2 2 2 +1 1 1 1 +0 0 0 0 +DROP TABLE t0, t1, t2, t3, t4; +# +# SJM Lookup with sort-nest, where SJM lookup table is outside the +# sort-nest +# +create table t1 (a int, b int, c int, key(a)); +create table t2 (a int, b int, c int, key(c)); +create table t3 (a int, b int, c int, key(a)); +create table t4 (a int, b int, c int); +INSERT INTO t1 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_10; +INSERT INTO t2 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_100; +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_1000; +INSERT INTO t4 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_100; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +ANALYZE TABLE t4 PERSISTENT FOR ALL; +set use_sort_nest= 1; +EXPLAIN SELECT t1.a, t2.a, t2.b +FROM t1, t2 +WHERE t2.a in (SELECT t3.b from t3) +AND t1.a= t2.b +AND t1.a < 5 +ORDER BY t1.b DESC, t2.a DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL a NULL NULL NULL 10 Using where +1 PRIMARY t2 ALL NULL NULL NULL NULL 100 Using where; Using join buffer (flat, BNL join) +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 5 Using filesort +1 PRIMARY <subquery2> eq_ref distinct_key distinct_key 4 func 1 +2 MATERIALIZED t3 ALL NULL NULL NULL NULL 1000 +EXPLAIN FORMAT=JSON SELECT t1.a, t2.a, t2.b +FROM t1, t2 +WHERE t2.a in (SELECT t3.b from t3) +AND t1.a= t2.b +AND t1.a < 5 +ORDER BY t1.b DESC, t2.a DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "possible_keys": ["a"], + "rows": 10, + "filtered": 50, + "attached_condition": "t1.a < 5" + }, + "block-nl-join": { + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 100, + "filtered": 5.4688 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL", + "attached_condition": "t2.b = t1.a" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.b desc, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + } + } + }, + "table": { + "table_name": "<subquery2>", + "access_type": "eq_ref", + "possible_keys": ["distinct_key"], + "key": "distinct_key", + "key_length": "4", + "used_key_parts": ["b"], + "ref": ["func"], + "rows": 1, + "filtered": 100, + "materialized": { + "unique": 1, + "query_block": { + "select_id": 2, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 1000, + "filtered": 100 + } + } + } + } + } +} +SELECT t1.a, t2.a, t2.b +FROM t1, t2 +WHERE t2.a in (SELECT t3.b from t3) +AND t1.a= t2.b +AND t1.a < 5 +ORDER BY t1.b DESC, t2.a DESC +LIMIT 5; +a a b +4 4 4 +3 3 3 +2 2 2 +1 1 1 +0 0 0 +set use_sort_nest= 0; +SELECT t1.a, t2.a, t2.b +FROM t1, t2 +WHERE t2.a in (SELECT t3.b from t3) +AND t1.a= t2.b +AND t1.a < 5 +ORDER BY t1.b DESC, t2.a DESC +LIMIT 5; +a a b +4 4 4 +3 3 3 +2 2 2 +1 1 1 +0 0 0 +DROP TABLE t1, t2, t3, t4; +# +# Firstmatch strategy +# +set @save_optimizer_switch=@@optimizer_switch; +create table t0(a int); +insert t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t1 (a int, b int, c int, key(a)); +insert t1 SELECT a,a,a from t0; +create table t2 as SELECT * from t1; +create table t3 as SELECT * from t1; +set use_sort_nest=1; +EXPLAIN SELECT * FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT b FROM t3 WHERE t3.c<=t2.c) +ORDER BY t2.c DESC, t1.c DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 10 Using where +1 PRIMARY t1 ref a a 5 test.t2.a 1 +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 5 Using filesort +1 PRIMARY t3 ALL NULL NULL NULL NULL 10 Using where; FirstMatch(<sort-nest>) +EXPLAIN FORMAT=JSON SELECT * FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT b FROM t3 WHERE t3.c<=t2.c) +ORDER BY t2.c DESC, t1.c DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "t2.a is not null" + }, + "table": { + "table_name": "t1", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["test.t2.a"], + "rows": 1, + "filtered": 100 + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.c desc, `sort-nest`.c desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "t3.b = `sort-nest`.b and t3.c <= `sort-nest`.c", + "first_match": "<sort-nest>" + } + } +} +SELECT * FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT b FROM t3 WHERE t3.c<=t2.c) +ORDER BY t2.c DESC, t1.c DESC +LIMIT 5; +a b c a b c +9 9 9 9 9 9 +8 8 8 8 8 8 +7 7 7 7 7 7 +6 6 6 6 6 6 +5 5 5 5 5 5 +set use_sort_nest=0; +SELECT * FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT b FROM t3 WHERE t3.c<=t2.c) +ORDER BY t2.c DESC, t1.c DESC +LIMIT 5; +a b c a b c +9 9 9 9 9 9 +8 8 8 8 8 8 +7 7 7 7 7 7 +6 6 6 6 6 6 +5 5 5 5 5 5 +set optimizer_switch='firstmatch=off'; +# +# Duplicate Weedout strategy +# +set use_sort_nest=1; +EXPLAIN SELECT * FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT b FROM t3 WHERE t3.c<=t2.c) +ORDER BY t2.c DESC, t1.c DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 10 Using where +1 PRIMARY t1 ref a a 5 test.t2.a 1 +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 5 Using filesort +1 PRIMARY t3 ALL NULL NULL NULL NULL 10 Using where; Start temporary; End temporary +EXPLAIN FORMAT=JSON SELECT * FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT b FROM t3 WHERE t3.c<=t2.c) +ORDER BY t2.c DESC, t1.c DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "t2.a is not null" + }, + "table": { + "table_name": "t1", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "key_length": "5", + "used_key_parts": ["a"], + "ref": ["test.t2.a"], + "rows": 1, + "filtered": 100 + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.c desc, `sort-nest`.c desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + } + } + }, + "duplicates_removal": { + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "t3.b = `sort-nest`.b and t3.c <= `sort-nest`.c" + } + } + } +} +SELECT * FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT b FROM t3 WHERE t3.c<=t2.c) +ORDER BY t2.c DESC, t1.c DESC +LIMIT 5; +a b c a b c +9 9 9 9 9 9 +8 8 8 8 8 8 +7 7 7 7 7 7 +6 6 6 6 6 6 +5 5 5 5 5 5 +set use_sort_nest=0; +SELECT * FROM t1, t2 +WHERE t1.a=t2.a AND +t1.b IN (SELECT b FROM t3 WHERE t3.c<=t2.c) +ORDER BY t2.c DESC, t1.c DESC +LIMIT 5; +a b c a b c +9 9 9 9 9 9 +8 8 8 8 8 8 +7 7 7 7 7 7 +6 6 6 6 6 6 +5 5 5 5 5 5 +set optimizer_switch=@save_optimizer_switch; +DROP TABLE t0,t1,t2,t3; + +# NON-MERGED SEMI JOINS + +create table t0 (a int); +INSERT INTO t0 SELECT seq-1 FROM seq_1_to_10; +create table t1 (a int, b int); +insert into t1 SELECT a,a from t0 where a <5; +create table t2 as SELECT * from t1 where a < 5; +create table t3(a int, b int); +INSERT INTO t3 SELECT seq-1, seq-1 FROM seq_1_to_100; +<subquery2> outside the sort-nest +set use_sort_nest=1; +EXPLAIN SELECT * from t2,t1 +WHERE t2.b=t1.b +AND +t1.a IN (SELECT max(t3.a) FROM t3 GROUP BY t3.b) +ORDER BY t2.a DESC,t1.a DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 5 +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 Using where; Using join buffer (flat, BNL join) +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 5 Using filesort +1 PRIMARY <subquery2> eq_ref distinct_key distinct_key 4 sort-nest.a 1 +2 MATERIALIZED t3 ALL NULL NULL NULL NULL 100 Using temporary +EXPLAIN FORMAT=JSON SELECT * from t2,t1 +WHERE t2.b=t1.b +AND +t1.a IN (SELECT max(t3.a) FROM t3 GROUP BY t3.b) +ORDER BY t2.a DESC,t1.a DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL", + "attached_condition": "t1.b = t2.b" + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.a desc, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + } + } + }, + "table": { + "table_name": "<subquery2>", + "access_type": "eq_ref", + "possible_keys": ["distinct_key"], + "key": "distinct_key", + "key_length": "4", + "used_key_parts": ["max(t3.a)"], + "ref": ["sort-nest.a"], + "rows": 1, + "filtered": 100, + "materialized": { + "unique": 1, + "query_block": { + "select_id": 2, + "temporary_table": { + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 100, + "filtered": 100 + } + } + } + } + } + } +} +SELECT * from t2,t1 +WHERE t2.b=t1.b +AND +t1.a IN (SELECT max(t3.a) FROM t3 GROUP BY t3.b) +ORDER BY t2.a DESC,t1.a DESC +LIMIT 5; +a b a b +4 4 4 4 +3 3 3 3 +2 2 2 2 +1 1 1 1 +0 0 0 0 +set use_sort_nest=0; +SELECT * from t2,t1 +WHERE t2.b=t1.b +AND +t1.a IN (SELECT max(t3.a) FROM t3 GROUP BY t3.b) +ORDER BY t2.a DESC,t1.a DESC +LIMIT 5; +a b a b +4 4 4 4 +3 3 3 3 +2 2 2 2 +1 1 1 1 +0 0 0 0 +<subquery2> inside the sort-nest +set use_sort_nest=1; +EXPLAIN SELECT * FROM t3,t2 +WHERE t3.b=t2.b AND +t3.a IN (SELECT max(t1.a) FROM t1 GROUP BY t1.b) +ORDER BY t3.a DESC,t2.a DESC +LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 5 +1 PRIMARY <subquery2> ALL distinct_key NULL NULL NULL 5 Using join buffer (flat, BNL join) +1 PRIMARY <sort-nest> ALL NULL NULL NULL NULL 1 Using filesort +1 PRIMARY t3 ALL NULL NULL NULL NULL 100 Using where +2 MATERIALIZED t1 ALL NULL NULL NULL NULL 5 Using temporary +EXPLAIN FORMAT=JSON SELECT * FROM t3,t2 +WHERE t3.b=t2.b AND +t3.a IN (SELECT max(t1.a) FROM t1 GROUP BY t1.b) +ORDER BY t3.a DESC,t2.a DESC +LIMIT 5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + }, + "block-nl-join": { + "table": { + "table_name": "<subquery2>", + "access_type": "ALL", + "possible_keys": ["distinct_key"], + "rows": 5, + "filtered": 100 + }, + "buffer_type": "flat", + "buffer_size": "119", + "join_type": "BNL", + "materialized": { + "unique": 1, + "query_block": { + "select_id": 2, + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 5, + "filtered": 100 + } + } + } + } + }, + "read_sorted_file": { + "filesort": { + "sort_key": "`sort-nest`.`max(t1.a)` desc, `sort-nest`.a desc", + "table": { + "table_name": "<sort-nest>", + "access_type": "ALL", + "rows": 1, + "filtered": 100 + } + } + }, + "table": { + "table_name": "t3", + "access_type": "ALL", + "rows": 100, + "filtered": 100, + "attached_condition": "t3.a = `sort-nest`.`max(t1.a)` and t3.b = `sort-nest`.b" + } + } +} +SELECT * FROM t3,t2 +WHERE t3.b=t2.b AND +t3.a IN (SELECT max(t1.a) FROM t1 GROUP BY t1.b) +ORDER BY t3.a DESC,t2.a DESC +LIMIT 5; +a b a b +4 4 4 4 +3 3 3 3 +2 2 2 2 +1 1 1 1 +0 0 0 0 +set use_sort_nest=0; +SELECT * FROM t3,t2 +WHERE t3.b=t2.b AND +t3.a IN (SELECT max(t1.a) FROM t1 GROUP BY t1.b) +ORDER BY t3.a DESC,t2.a DESC +LIMIT 5; +a b a b +4 4 4 4 +3 3 3 3 +2 2 2 2 +1 1 1 1 +0 0 0 0 +DROP TABLE t1,t2,t3,t0; diff --git a/mysql-test/main/sort_nest_sj.test b/mysql-test/main/sort_nest_sj.test new file mode 100644 index 00000000000..2a06da7097c --- /dev/null +++ b/mysql-test/main/sort_nest_sj.test @@ -0,0 +1,201 @@ +--source include/have_sequence.inc + +--echo # +--echo # SORT-NEST WITH SEMI JOINS +--echo # + +--echo +--echo # MERGED SEMI-JOINS +--echo + +--echo # SEMI JOIN MATERIALIZATION SCAN with SORT-NEST + +CREATE TABLE t0(a int); +CREATE TABLE t1 (a int, b int, c int); +CREATE TABLE t2 (a int, b int, c int); +CREATE TABLE t3 (a int, b int, c int, key(a)); +CREATE TABLE t4 (a int, b int, c int, key(a)); + +INSERT INTO t0 SELECT seq-1 FROM seq_1_to_10; +INSERT INTO t1 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_100; +INSERT INTO t2 SELECT a,a,a FROM t0; +INSERT INTO t3 SELECT a,a,a FROM t0; +INSERT INTO t4 SELECT a,a,a FROM t0; + +--disable_result_log +ANALYZE TABLE t0 PERSISTENT FOR ALL; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +ANALYZE TABLE t4 PERSISTENT FOR ALL; +--enable_result_log + +--echo # SJM scan inside the sort-nest +--echo # sort-nest includes (t2, <subquery2>) + +let $query= SELECT t1.a, t2.a, t1.b,t2.b + FROM t1, t2 + WHERE t1.a=t2.a AND + t1.b IN (SELECT t3.b FROM t3,t4 + WHERE t3.a < 3 AND t3.a=t4.a) + ORDER BY t1.b DESC ,t2.b DESC + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set use_sort_nest=0; +eval $query; + +--echo # +--echo # SJM scan table is the first table inside the sort-nest +--echo # + +alter table t2 add key(b); +let $query= SELECT t1.a, t2.a, t1.b,t2.b + FROM t1, t2 + WHERE t1.a=t2.a AND t2.b < 5 AND + t1.b IN (SELECT t3.b FROM t3,t4 + WHERE t3.a < 3 AND t3.a=t4.a) + ORDER BY t2.b DESC, t1.b DESC + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval $query; + +set use_sort_nest= 0; +eval $query; + +DROP TABLE t0, t1, t2, t3, t4; + +--echo # +--echo # SJM Lookup with sort-nest, where SJM lookup table is outside the +--echo # sort-nest +--echo # + +create table t1 (a int, b int, c int, key(a)); +create table t2 (a int, b int, c int, key(c)); +create table t3 (a int, b int, c int, key(a)); +create table t4 (a int, b int, c int); + +INSERT INTO t1 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_10; +INSERT INTO t2 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_100; +INSERT INTO t3 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_1000; +INSERT INTO t4 SELECT seq-1, seq-1, seq-1 FROM seq_1_to_100; + +--disable_result_log +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ANALYZE TABLE t2 PERSISTENT FOR ALL; +ANALYZE TABLE t3 PERSISTENT FOR ALL; +ANALYZE TABLE t4 PERSISTENT FOR ALL; +--enable_result_log + +let $query= SELECT t1.a, t2.a, t2.b + FROM t1, t2 + WHERE t2.a in (SELECT t3.b from t3) + AND t1.a= t2.b + AND t1.a < 5 + ORDER BY t1.b DESC, t2.a DESC + LIMIT 5; + +set use_sort_nest= 1; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set use_sort_nest= 0; +eval $query; + +DROP TABLE t1, t2, t3, t4; + +--echo # +--echo # Firstmatch strategy +--echo # + +set @save_optimizer_switch=@@optimizer_switch; +create table t0(a int); +insert t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t1 (a int, b int, c int, key(a)); +insert t1 SELECT a,a,a from t0; +create table t2 as SELECT * from t1; +create table t3 as SELECT * from t1; +let $query= SELECT * FROM t1, t2 + WHERE t1.a=t2.a AND + t1.b IN (SELECT b FROM t3 WHERE t3.c<=t2.c) + ORDER BY t2.c DESC, t1.c DESC + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set use_sort_nest=0; +eval $query; + +set optimizer_switch='firstmatch=off'; + +--echo # +--echo # Duplicate Weedout strategy +--echo # + +set use_sort_nest=1; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set use_sort_nest=0; +eval $query; + +set optimizer_switch=@save_optimizer_switch; +DROP TABLE t0,t1,t2,t3; + +--echo +--echo # NON-MERGED SEMI JOINS +--echo + +create table t0 (a int); +INSERT INTO t0 SELECT seq-1 FROM seq_1_to_10; +create table t1 (a int, b int); +insert into t1 SELECT a,a from t0 where a <5; +create table t2 as SELECT * from t1 where a < 5; +create table t3(a int, b int); +INSERT INTO t3 SELECT seq-1, seq-1 FROM seq_1_to_100; + +--echo <subquery2> outside the sort-nest + +let $query= SELECT * from t2,t1 + WHERE t2.b=t1.b + AND + t1.a IN (SELECT max(t3.a) FROM t3 GROUP BY t3.b) + ORDER BY t2.a DESC,t1.a DESC + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set use_sort_nest=0; +eval $query; + +--echo <subquery2> inside the sort-nest + +let $query= SELECT * FROM t3,t2 + WHERE t3.b=t2.b AND + t3.a IN (SELECT max(t1.a) FROM t1 GROUP BY t1.b) + ORDER BY t3.a DESC,t2.a DESC + LIMIT 5; + +set use_sort_nest=1; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set use_sort_nest=0; +eval $query; + +DROP TABLE t1,t2,t3,t0; diff --git a/mysql-test/main/sort_nest_subselect.test b/mysql-test/main/sort_nest_subselect.test new file mode 100644 index 00000000000..e34a4817499 --- /dev/null +++ b/mysql-test/main/sort_nest_subselect.test @@ -0,0 +1,99 @@ +--echo # +--echo # Testing SORT-NEST with non-flattened Subqueries +--echo # + +--echo # +--echo # Dependent subquery attached to table t3 outside the sort-nest(t1,t2) +--echo # + +set use_sort_nest=1; +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t1 (a int, b int); +insert into t1 select a,a from t0 where a <5; # 5 rows +create table t2 as select * from t1 where a < 5; # 5 rows +create table t3(a int, b int, c int); +insert into t3 select A.a + 10*B.a, A.a + 10*B.a, A.a + 10*B.a from t0 A, t0 B; # 100 rows + +create table t4(a int, b int, c int, key(b)); +insert into t4 select A.a + 10*B.a, A.a + 10*B.a, A.a + 10*B.a from t0 A, t0 B; # 100 rows + +--echo # ref access inside the dependent subquery should be with sort-nest.b instead of t1.b +--echo # subquery is attached to table t3 which is outside the sort-nest + +let $query= SELECT * FROM t1,t2,t3 + WHERE t1.b=t2.b and + EXISTS (select 1 from t4 where t4.b=t1.b and t4.b < 4 group by t4.c having t3.b=max(t4.a)) + ORDER BY t2.a desc,t1.a desc + LIMIT 5; + +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +--echo # same as above but exists to in transformation not allowed +--echo # subquery is attached to table t3 which is outside the sort-nest + +set optimizer_switch='exists_to_in=off'; +eval EXPLAIN $query; +eval EXPLAIN FORMAT=JSON $query; +eval $query; + +set optimizer_switch=default; + +drop table t0,t1,t2,t3,t4; + + +--echo # DEPENDENT SUBQUERIES + +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t1 (a int, b int); +insert into t1 select a,a from t0 where a <5; +create table t2 as select * from t1 where a < 5; +create table t3 as select (A.a + 10*B.a+C.a*100) as a, (A.a + 10*B.a+C.a*100) as b, + (A.a + 10*B.a+C.a*100) as c from t0 A, t0 B,t0 C; # 1000 rows + +set optimizer_switch='exists_to_in=off'; + +--echo # sort-nest(t2,t1) and subquery should be attached to table ot1 +let $query= select * from t2,t1,t3 + where exists (select max(t3.a) from t3 t4 where t4.b=t1.b group by t4.c having t3.a= max(t4.a)) + order by t2.a desc,t1.a desc limit 5; + +eval explain $query; +eval explain format=json $query; +eval $query; + +set optimizer_switch=default; +--echo # sort-nest(t2,t1) and subquery should be attached to table ot1 (same as above) +let $query= select * from t2,t1,t3 + where t3.a in (select max(t4.a) from t3 t4 where t4.b=t1.b group by t4.c) + order by t2.a desc,t1.a desc limit 5; + +eval explain $query; +eval explain format=json $query; +eval $query; + +--echo # sort-nest(t2,t1) and dependent subquery in the select list +let $query= select (select t4.a from t3 t4 where t4.a > t1.b limit 1) as x, t2.b, t1.b, t3.a from t1,t2,t3 + where t1.a = t2.a order by t2.b desc, t1.b desc limit 5; + +eval explain $query; +eval explain format=json $query; +eval $query; + +--echo # +--echo # sort-nest(t2,t1) and sort-nest fields substitution in the having clause of the subquery +--echo # after IN -> EXISTS transformation +--echo # + +let $query= select * from t2,t1,t3 ot1 + where t2.a+ot1.a in (select max(t3.a) from t3 where t3.b=t1.b group by t3.c) + order by t2.a desc,t1.a desc limit 5; + +eval explain $query; +eval explain format=json $query; +eval $query; + +drop table t0,t1,t2,t3; diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index da046d0ec3a..1fb3fe61644 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -150,6 +150,7 @@ SET (SQL_SOURCE sql_cte.cc item_vers.cc sql_sequence.cc sql_sequence.h ha_sequence.h + sql_sort_nest.cc sql_tvc.cc sql_tvc.h opt_split.cc rowid_filter.cc rowid_filter.h diff --git a/sql/item.cc b/sql/item.cc index d837e8b5719..d584c5e8bc5 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -586,7 +586,8 @@ bool Item::cleanup_processor(void *arg) pointer to newly allocated item is returned. */ -Item* Item::transform(THD *thd, Item_transformer transformer, uchar *arg) +Item* Item::transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg) { DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); @@ -6181,6 +6182,52 @@ Item *Item_field::replace_equal_field(THD *thd, uchar *arg) } +/* + Replace any Item_field object with the item in the nest table. + This is needed to substitute the item that are evaluated in the + post ORDER BY context that is when a sort-nest was created + and the ordering was already done. + + @param arg NULL or points to the JOIN structure + + @note + This function is supposed to be called as a callback parameter in calls + of the transformer method. + + @return + - pointer to the nest items corresponding to the current item + - this - otherwise. +*/ + +Item *Item_field::replace_with_nest_items(THD *thd, uchar *arg) +{ + Mat_join_tab_nest_info *nest_info= (Mat_join_tab_nest_info*)arg; + + if (!(used_tables() & nest_info->get_tables_map()) && + !(used_tables() & OUTER_REF_TABLE_BIT)) + return this; + + List_iterator_fast<Item_pair> li(nest_info->mapping_of_items); + Item_pair *item_pair; + while((item_pair= li++)) + { + Item *field_item= item_pair->base_item->real_item(); + if (field->eq(((Item_field*)field_item)->field)) + { + if (used_tables() == OUTER_REF_TABLE_BIT) + { + Item_field *clone_item= new (thd->mem_root) Item_field(thd, this); + Item *nest_item= item_pair->nest_item; + clone_item->set_field(((Item_field*)nest_item)->field); + return clone_item; + } + return item_pair->nest_item; + } + } + return this; +} + + void Item::init_make_send_field(Send_field *tmp_field, const Type_handler *h) { @@ -7217,7 +7264,7 @@ Item *Item_field::update_value_transformer(THD *thd, uchar *select_arg) @note This method is called for pushdown conditions into materialized derived tables/views optimization. - Item::pushable_cond_checker_for_derived() is passed as the actual callback + Item::pushable_cond_checker_for_tables() is passed as the actual callback function. Also it is called for pushdown conditions in materialized IN subqueries. Item::pushable_cond_checker_for_subquery is passed as the actual @@ -7257,6 +7304,86 @@ void Item::check_pushable_cond(Pushdown_checker checker, uchar *arg) /** @brief + For a condition check possibility of exraction a formula over grouping fields + + @param thd The thread handle + @param cond The condition whose subformulas are to be analyzed + @param checker The checker callback function to be applied to the nodes + of the tree of the object + + @details + This method traverses the AND-OR condition cond and for each subformula of + the condition it checks whether it can be usable for the extraction of a + condition over the grouping fields of this select. The method uses + the call-back parameter checker to check whether a primary formula + depends only on grouping fields. + The subformulas that are not usable are marked with the flag NO_EXTRACTION_FL. + The subformulas that can be entierly extracted are marked with the flag + FULL_EXTRACTION_FL. + @note + This method is called before any call of extract_cond_for_grouping_fields. + The flag NO_EXTRACTION_FL set in a subformula allows to avoid building clone + for the subformula when extracting the pushable condition. + The flag FULL_EXTRACTION_FL allows to delete later all top level conjuncts + from cond. + + TODO varun: + rewrite the comments to make sense + also a note for sort-nest using this would make sense + +*/ + +void Item::check_pushable_cond_extraction(Pushdown_checker checker, uchar *arg) +{ + if (get_extraction_flag() == NO_EXTRACTION_FL) + return; + clear_extraction_flag(); + if (type() == Item::COND_ITEM) + { + Item_cond_and *and_cond= + (((Item_cond*) this)->functype() == Item_func::COND_AND_FUNC) ? + ((Item_cond_and*) this) : 0; + + List<Item> *arg_list= ((Item_cond*) this)->argument_list(); + List_iterator<Item> li(*arg_list); + uint count= 0; // to count items not containing NO_EXTRACTION_FL + uint count_full= 0; // to count items with FULL_EXTRACTION_FL + Item *item; + while ((item=li++)) + { + item->check_pushable_cond_extraction(checker, arg); + if (item->get_extraction_flag() != NO_EXTRACTION_FL) + { + count++; + if (item->get_extraction_flag() == FULL_EXTRACTION_FL) + count_full++; + } + else if (!and_cond) + break; + } + if ((and_cond && count == 0) || item) + set_extraction_flag(NO_EXTRACTION_FL); + if (count_full == arg_list->elements) + { + set_extraction_flag(FULL_EXTRACTION_FL); + } + if (get_extraction_flag() != 0) + { + li.rewind(); + while ((item=li++)) + item->clear_extraction_flag(); + } + } + else + { + int fl= ((this->*checker) (arg)) ? + FULL_EXTRACTION_FL : NO_EXTRACTION_FL; + set_extraction_flag(fl); + } +} + +/** + @brief Build condition extractable from this condition for pushdown @param thd the thread handle @@ -7386,6 +7513,99 @@ Item *Item::build_pushable_cond(THD *thd, return 0; } +/** + @brief + Build condition extractable from the given one depended on grouping fields + + @param thd The thread handle + @param cond The condition from which the condition depended + on grouping fields is to be extracted + @param no_top_clones If it's true then no clones for the top MODE_ONLY_FULL_GROUP_BY + extractable conjuncts are built + + @details + For the given condition cond this method finds out what condition depended + only on the grouping fields can be extracted from cond. If such condition C + exists the method builds the item for it. + This method uses the flags NO_EXTRACTION_FL and FULL_EXTRACTION_FL set by the + preliminary call of st_select_lex::check_cond_extraction_for_grouping_fields + to figure out whether a subformula depends only on these fields or not. + @note + The built condition C is always implied by the condition cond + (cond => C). The method tries to build the least restictive such + condition (i.e. for any other condition C' such that cond => C' + we have C => C'). + @note + The build item is not ready for usage: substitution for the field items + has to be done and it has to be re-fixed. + + @retval + the built condition depended only on grouping fields if such a condition + exists + NULL if there is no such a condition +*/ + +Item *Item::build_pushable_condition(THD *thd, bool no_top_clones) +{ + if (get_extraction_flag() == FULL_EXTRACTION_FL) + { + if (no_top_clones) + return this; + clear_extraction_flag(); + return build_clone(thd); + } + if (type() == Item::COND_ITEM) + { + bool cond_and= false; + Item_cond *new_cond; + if (((Item_cond*) this)->functype() == Item_func::COND_AND_FUNC) + { + cond_and= true; + new_cond= new (thd->mem_root) Item_cond_and(thd); + } + else + new_cond= new (thd->mem_root) Item_cond_or(thd); + if (unlikely(!new_cond)) + return 0; + List_iterator<Item> li(*((Item_cond*) this)->argument_list()); + Item *item; + while ((item=li++)) + { + if (item->get_extraction_flag() == NO_EXTRACTION_FL) + { + DBUG_ASSERT(cond_and); + item->clear_extraction_flag(); + continue; + } + Item *fix= item->build_pushable_condition(thd, no_top_clones & cond_and); + if (unlikely(!fix)) + { + if (cond_and) + continue; + break; + } + new_cond->argument_list()->push_back(fix, thd->mem_root); + } + + if (!cond_and && item) + { + while((item= li++)) + item->clear_extraction_flag(); + return 0; + } + switch (new_cond->argument_list()->elements) + { + case 0: + return 0; + case 1: + return new_cond->argument_list()->head(); + default: + return new_cond; + } + } + return 0; +} + static Item *get_field_item_for_having(THD *thd, Item *item, st_select_lex *sel) @@ -7985,13 +8205,15 @@ void Item_ref::cleanup() @retval NULL Out of memory error */ -Item* Item_ref::transform(THD *thd, Item_transformer transformer, uchar *arg) +Item* Item_ref::transform(THD *thd, Item_transformer transformer, + bool transform_subquery,uchar *arg) { DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); DBUG_ASSERT((*ref) != NULL); /* Transform the object we are referencing. */ - Item *new_item= (*ref)->transform(thd, transformer, arg); + Item *new_item= (*ref)->transform(thd, transformer, + transform_subquery, arg); if (!new_item) return NULL; @@ -9099,10 +9321,12 @@ Item *Item_direct_view_ref::replace_equal_field(THD *thd, uchar *arg) } -bool Item_field::excl_dep_on_table(table_map tab_map) +bool Item_field::excl_dep_on_tables(table_map tab_map, bool multi_eq_checked) { - return used_tables() == tab_map || - (item_equal && (item_equal->used_tables() & tab_map)); + return !(used_tables() & ~tab_map) || + (multi_eq_checked ? + FALSE: + (item_equal && (item_equal->used_tables() & tab_map))); } @@ -9113,7 +9337,8 @@ Item_field::excl_dep_on_grouping_fields(st_select_lex *sel) } -bool Item_direct_view_ref::excl_dep_on_table(table_map tab_map) +bool Item_direct_view_ref::excl_dep_on_tables(table_map tab_map, + bool multi_eq_checked) { table_map used= used_tables(); if (used & OUTER_REF_TABLE_BIT) @@ -9125,7 +9350,7 @@ bool Item_direct_view_ref::excl_dep_on_table(table_map tab_map) DBUG_ASSERT(real_item()->type() == Item::FIELD_ITEM); return item_equal->used_tables() & tab_map; } - return (*ref)->excl_dep_on_table(tab_map); + return (*ref)->excl_dep_on_tables(tab_map, multi_eq_checked); } @@ -9367,7 +9592,7 @@ table_map Item_default_value::used_tables() const */ Item *Item_default_value::transform(THD *thd, Item_transformer transformer, - uchar *args) + bool transform_subquery, uchar *args) { DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); @@ -9378,7 +9603,8 @@ Item *Item_default_value::transform(THD *thd, Item_transformer transformer, if (!arg) return 0; - Item *new_item= arg->transform(thd, transformer, args); + Item *new_item= arg->transform(thd, transformer, + transform_subquery, args); if (!new_item) return 0; diff --git a/sql/item.h b/sql/item.h index 2ba7ba6874d..74fba614839 100644 --- a/sql/item.h +++ b/sql/item.h @@ -447,6 +447,7 @@ typedef struct replace_equal_field_arg struct st_join_table *context_tab; } REPLACE_EQUAL_FIELD_ARG; + class Settable_routine_parameter { public: @@ -1840,7 +1841,8 @@ public: return (this->*processor)(arg); } - virtual Item* transform(THD *thd, Item_transformer transformer, uchar *arg); + virtual Item* transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg); /* This function performs a generic "compilation" of the Item tree. @@ -1917,8 +1919,14 @@ public: TRUE if the expression depends only on the table indicated by tab_map or can be converted to such an exression using equalities. Not to be used for AND/OR formulas. + + @param multi_eq_checked set to TRUE if substitution for best field + item inside the multiple equality is already + done */ - virtual bool excl_dep_on_table(table_map tab_map) { return false; } + virtual bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked) + { return false; } + /* TRUE if the expression depends only on grouping fields of sel or can be converted to such an expression using equalities. @@ -2181,6 +2189,8 @@ public: { return this; } virtual Item *multiple_equality_transformer(THD *thd, uchar *arg) { return this; } + virtual Item *replace_with_nest_items(THD *thd, uchar *arg) + { return this; } virtual bool expr_cache_is_needed(THD *) { return FALSE; } virtual Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs); bool needs_charset_converter(uint32 length, CHARSET_INFO *tocs) const @@ -2376,10 +2386,32 @@ public: marker &= ~EXTRACTION_MASK; } void check_pushable_cond(Pushdown_checker excl_dep_func, uchar *arg); - bool pushable_cond_checker_for_derived(uchar *arg) + + void check_pushable_cond_extraction(Pushdown_checker checker, uchar *arg); + + /* + This function is used for the cases when we don't want to take into account + multiple equalities while finding dependency of items on tables. + */ + bool pushable_cond_checker_for_tables(uchar *arg) { - return excl_dep_on_table(*((table_map *)arg)); + return excl_dep_on_tables(*(table_map *)arg, TRUE); } + + /* + This function is used for the cases when we want to take into account + multiple equalities while finding dependency of items on tables. + */ + bool pushable_cond_checker_for_tables_with_equalities(uchar *arg) + { + return excl_dep_on_tables(*(table_map *)arg, FALSE); + } + + bool pushable_cond_checker_for_grouping_fields(uchar *arg) + { + return excl_dep_on_grouping_fields((st_select_lex*)arg); + } + bool pushable_cond_checker_for_subquery(uchar *arg) { return excl_dep_on_in_subq_left_part((Item_in_subselect *)arg); @@ -2387,6 +2419,7 @@ public: Item *build_pushable_cond(THD *thd, Pushdown_checker checker, uchar *arg); + Item *build_pushable_condition(THD *thd, bool no_top_clones); /* Checks if this item depends only on the arg table */ @@ -2515,15 +2548,16 @@ protected: } return false; } - bool transform_args(THD *thd, Item_transformer transformer, uchar *arg); + bool transform_args(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg); void propagate_equal_fields(THD *, const Item::Context &, COND_EQUAL *); - bool excl_dep_on_table(table_map tab_map) + bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked) { for (uint i= 0; i < arg_count; i++) { if (args[i]->const_item()) continue; - if (!args[i]->excl_dep_on_table(tab_map)) + if (!args[i]->excl_dep_on_tables(tab_map, multi_eq_checked)) return false; } return true; @@ -3488,7 +3522,7 @@ public: Item *in_subq_field_transformer_for_where(THD *thd, uchar *arg); Item *in_subq_field_transformer_for_having(THD *thd, uchar *arg); virtual void print(String *str, enum_query_type query_type); - bool excl_dep_on_table(table_map tab_map); + bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked); bool excl_dep_on_grouping_fields(st_select_lex *sel); bool excl_dep_on_in_subq_left_part(Item_in_subselect *subq_pred); bool cleanup_excluding_fields_processor(void *arg) @@ -3504,6 +3538,7 @@ public: return field->table->pos_in_table_list->outer_join; } bool check_index_dependence(void *arg); + Item *replace_with_nest_items(THD *thd, uchar *arg); friend class Item_default_value; friend class Item_insert_value; friend class st_select_lex_unit; @@ -5266,7 +5301,8 @@ public: else return FALSE; } - Item* transform(THD *thd, Item_transformer, uchar *arg); + Item* transform(THD *thd, Item_transformer, + bool transform_subquery, uchar *arg); Item* compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, Item_transformer transformer, uchar *arg_t); bool enumerate_field_refs_processor(void *arg) @@ -5339,13 +5375,15 @@ public: } Item *get_copy(THD *thd) { return get_item_copy<Item_ref>(thd, this); } - bool excl_dep_on_table(table_map tab_map) + bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked) { table_map used= used_tables(); if (used & OUTER_REF_TABLE_BIT) return false; - return (used == tab_map) || (*ref)->excl_dep_on_table(tab_map); + return (!(used & ~tab_map) || + (*ref)->excl_dep_on_tables(tab_map, multi_eq_checked)); } + bool excl_dep_on_grouping_fields(st_select_lex *sel) { return (*ref)->excl_dep_on_grouping_fields(sel); } bool excl_dep_on_in_subq_left_part(Item_in_subselect *subq_pred) @@ -5671,7 +5709,7 @@ public: view_arg->view_used_tables|= (*ref)->used_tables(); return 0; } - bool excl_dep_on_table(table_map tab_map); + bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked); bool excl_dep_on_grouping_fields(st_select_lex *sel); bool excl_dep_on_in_subq_left_part(Item_in_subselect *subq_pred); Item *derived_field_transformer_for_having(THD *thd, uchar *arg); @@ -6270,7 +6308,8 @@ public: (this->*processor)(args); } - Item *transform(THD *thd, Item_transformer transformer, uchar *args); + Item *transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *args); }; /** @@ -6580,6 +6619,22 @@ public: return TRUE; return (this->*processor)(arg); } + // TODO:varun need to enable this + Item *transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg) + { + if (transform_subquery) + { + if (example) + { + Item *new_item= example->transform(thd, transformer, + transform_subquery, arg); + if (new_item != example) + setup(thd, new_item); + } + } + return (this->*transformer)(thd, arg); + } virtual Item *safe_charset_converter(THD *thd, CHARSET_INFO *tocs); void split_sum_func2_example(THD *thd, Ref_ptr_array ref_pointer_array, List<Item> &fields, uint flags) diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 6df2b5dbd3a..94be07d8d11 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -1705,7 +1705,7 @@ bool Item_in_optimizer::is_null() */ Item *Item_in_optimizer::transform(THD *thd, Item_transformer transformer, - uchar *argument) + bool transform_subquery, uchar *argument) { Item *new_item; @@ -1714,7 +1714,8 @@ Item *Item_in_optimizer::transform(THD *thd, Item_transformer transformer, DBUG_ASSERT(arg_count == 2); /* Transform the left IN operand. */ - new_item= (*args)->transform(thd, transformer, argument); + new_item= (*args)->transform(thd, transformer, + transform_subquery, argument); if (!new_item) return 0; /* @@ -1726,10 +1727,18 @@ Item *Item_in_optimizer::transform(THD *thd, Item_transformer transformer, if ((*args) != new_item) thd->change_item_tree(args, new_item); + /* + TODO varun + needs to transform the cached item + */ + if (transform_subquery) + cache->setup(thd, args[0]); + if (invisible_mode()) { /* MAX/MIN transformed => pass through */ - new_item= args[1]->transform(thd, transformer, argument); + new_item= args[1]->transform(thd, transformer, + transform_subquery, argument); if (!new_item) return 0; if (args[1] != new_item) @@ -1754,6 +1763,18 @@ Item *Item_in_optimizer::transform(THD *thd, Item_transformer transformer, Item_in_subselect *in_arg= (Item_in_subselect*)args[1]; thd->change_item_tree(&in_arg->left_expr, args[0]); + + /* + TODO(varun): this is needed for the sort-nest when we have dependent + subqyueries, for such cases we would need to introduce a new + parameter to transform function like transform_subquery, + if set to TRUE we would change the inner contents of the + subquery also. + + new_item= args[1]->transform(thd, transformer, argument); + if (args[1] != new_item) + thd->change_item_tree(args + 1, new_item); + */ } return (this->*transformer)(thd, argument); } @@ -5110,7 +5131,8 @@ bool Item_cond::walk(Item_processor processor, bool walk_subquery, void *arg) Item returned as the result of transformation of the root node */ -Item *Item_cond::transform(THD *thd, Item_transformer transformer, uchar *arg) +Item *Item_cond::transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg) { DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); @@ -5118,7 +5140,8 @@ Item *Item_cond::transform(THD *thd, Item_transformer transformer, uchar *arg) Item *item; while ((item= li++)) { - Item *new_item= item->transform(thd, transformer, arg); + Item *new_item= item->transform(thd, transformer, + transform_subquery, arg); if (!new_item) return 0; @@ -5131,7 +5154,7 @@ Item *Item_cond::transform(THD *thd, Item_transformer transformer, uchar *arg) if (new_item != item) thd->change_item_tree(li.ref(), new_item); } - return Item_func::transform(thd, transformer, arg); + return Item_func::transform(thd, transformer, transform_subquery, arg); } @@ -5178,7 +5201,7 @@ Item *Item_cond::compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, if (new_item && new_item != item) thd->change_item_tree(li.ref(), new_item); } - return Item_func::transform(thd, transformer, arg_t); + return Item_func::transform(thd, transformer, FALSE, arg_t); } @@ -5330,7 +5353,7 @@ Item *Item_cond::build_clone(THD *thd) } -bool Item_cond::excl_dep_on_table(table_map tab_map) +bool Item_cond::excl_dep_on_tables(table_map tab_map, bool multi_eq_checked) { if (used_tables() & OUTER_REF_TABLE_BIT) return false; @@ -5340,7 +5363,7 @@ bool Item_cond::excl_dep_on_table(table_map tab_map) Item *item; while ((item= li++)) { - if (!item->excl_dep_on_table(tab_map)) + if (!item->excl_dep_on_tables(tab_map, multi_eq_checked)) return false; } return true; @@ -7164,7 +7187,8 @@ bool Item_equal::walk(Item_processor processor, bool walk_subquery, void *arg) } -Item *Item_equal::transform(THD *thd, Item_transformer transformer, uchar *arg) +Item *Item_equal::transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg) { DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); @@ -7172,7 +7196,8 @@ Item *Item_equal::transform(THD *thd, Item_transformer transformer, uchar *arg) Item_equal_fields_iterator it(*this); while ((item= it++)) { - Item *new_item= item->transform(thd, transformer, arg); + Item *new_item= item->transform(thd, transformer, + transform_subquery, arg); if (!new_item) return 0; @@ -7185,7 +7210,7 @@ Item *Item_equal::transform(THD *thd, Item_transformer transformer, uchar *arg) if (new_item != item) thd->change_item_tree((Item **) it.ref(), new_item); } - return Item_func::transform(thd, transformer, arg); + return Item_func::transform(thd, transformer, transform_subquery, arg); } diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 9b20fa50214..e65521d35e8 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -377,7 +377,8 @@ public: const char *func_name() const { return "<in_optimizer>"; } Item_cache **get_cache() { return &cache; } void keep_top_level_cache(); - Item *transform(THD *thd, Item_transformer transformer, uchar *arg); + Item *transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg); virtual Item *expr_cache_insert_transformer(THD *thd, uchar *unused); bool is_expensive_processor(void *arg); bool is_expensive(); @@ -2997,7 +2998,8 @@ public: bool top_level() { return abort_on_null; } void copy_andor_arguments(THD *thd, Item_cond *item); bool walk(Item_processor processor, bool walk_subquery, void *arg); - Item *transform(THD *thd, Item_transformer transformer, uchar *arg); + Item *transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg); void traverse_cond(Cond_traverser, void *arg, traverse_order order); void neg_arguments(THD *thd); Item* propagate_equal_fields(THD *, const Context &, COND_EQUAL *); @@ -3006,7 +3008,7 @@ public: bool eval_not_null_tables(void *opt_arg); bool find_not_null_fields(table_map allowed); Item *build_clone(THD *thd); - bool excl_dep_on_table(table_map tab_map); + bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked); bool excl_dep_on_grouping_fields(st_select_lex *sel); }; @@ -3179,7 +3181,8 @@ public: SARGABLE_PARAM **sargables); SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param, Item **cond_ptr); bool walk(Item_processor processor, bool walk_subquery, void *arg); - Item *transform(THD *thd, Item_transformer transformer, uchar *arg); + Item *transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg); virtual void print(String *str, enum_query_type query_type); const Type_handler *compare_type_handler() const { return m_compare_handler; } CHARSET_INFO *compare_collation() const { return m_compare_collation; } @@ -3191,7 +3194,7 @@ public: This does not comply with the specification of the virtual method, but Item_equal items are processed distinguishly anyway */ - bool excl_dep_on_table(table_map tab_map) + bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked) { return used_tables() & tab_map; } diff --git a/sql/item_func.cc b/sql/item_func.cc index 0419d55cc58..f0731dad350 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -474,11 +474,13 @@ void Item_func::traverse_cond(Cond_traverser traverser, } -bool Item_args::transform_args(THD *thd, Item_transformer transformer, uchar *arg) +bool Item_args::transform_args(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg) { for (uint i= 0; i < arg_count; i++) { - Item *new_item= args[i]->transform(thd, transformer, arg); + Item *new_item= args[i]->transform(thd, transformer, + transform_subquery, arg); if (!new_item) return true; /* @@ -511,10 +513,11 @@ bool Item_args::transform_args(THD *thd, Item_transformer transformer, uchar *ar Item returned as the result of transformation of the root node */ -Item *Item_func::transform(THD *thd, Item_transformer transformer, uchar *argument) +Item *Item_func::transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *argument) { DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); - if (transform_args(thd, transformer, argument)) + if (transform_args(thd, transformer, transform_subquery, argument)) return 0; return (this->*transformer)(thd, argument); } diff --git a/sql/item_func.h b/sql/item_func.h index dced158bb86..ca22aee8d1b 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -204,7 +204,8 @@ public: else max_length= (uint32) max_result_length; } - Item *transform(THD *thd, Item_transformer transformer, uchar *arg); + Item *transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg); Item* compile(THD *thd, Item_analyzer analyzer, uchar **arg_p, Item_transformer transformer, uchar *arg_t); void traverse_cond(Cond_traverser traverser, @@ -331,12 +332,12 @@ public: return used_tables() & RAND_TABLE_BIT; } - bool excl_dep_on_table(table_map tab_map) + bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked) { if (used_tables() & OUTER_REF_TABLE_BIT) return false; return !(used_tables() & ~tab_map) || - Item_args::excl_dep_on_table(tab_map); + Item_args::excl_dep_on_tables(tab_map, multi_eq_checked); } bool excl_dep_on_grouping_fields(st_select_lex *sel) @@ -2875,7 +2876,8 @@ public: void cleanup(); Item *get_copy(THD *thd) { return get_item_copy<Item_func_set_user_var>(thd, this); } - bool excl_dep_on_table(table_map tab_map) { return false; } + bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked) + { return false; } }; diff --git a/sql/item_row.cc b/sql/item_row.cc index def1458df1b..d6fd2938d15 100644 --- a/sql/item_row.cc +++ b/sql/item_row.cc @@ -166,11 +166,12 @@ void Item_row::print(String *str, enum_query_type query_type) } -Item *Item_row::transform(THD *thd, Item_transformer transformer, uchar *arg) +Item *Item_row::transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg) { DBUG_ASSERT(!thd->stmt_arena->is_stmt_prepare()); - if (transform_args(thd, transformer, arg)) + if (transform_args(thd, transformer, transform_subquery, arg)) return 0; return (this->*transformer)(thd, arg); } diff --git a/sql/item_row.h b/sql/item_row.h index 2872a498d55..75ec99499f3 100644 --- a/sql/item_row.h +++ b/sql/item_row.h @@ -119,7 +119,8 @@ public: return true; return (this->*processor)(arg); } - Item *transform(THD *thd, Item_transformer transformer, uchar *arg); + Item *transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg); bool eval_not_null_tables(void *opt_arg); bool find_not_null_fields(table_map allowed); @@ -136,9 +137,9 @@ public: return this; } - bool excl_dep_on_table(table_map tab_map) + bool excl_dep_on_tables(table_map tab_map, bool multi_eq_checked) { - return Item_args::excl_dep_on_table(tab_map); + return Item_args::excl_dep_on_tables(tab_map, multi_eq_checked); } bool excl_dep_on_grouping_fields(st_select_lex *sel) diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 2d3ea388cc5..cef783d9ec8 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -715,6 +715,80 @@ bool Item_subselect::walk(Item_processor processor, bool walk_subquery, } +Item* Item_subselect::transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg) +{ + if (!(unit->uncacheable & ~UNCACHEABLE_DEPENDENT) && engine->is_executed() && + !unit->describe) + { + /* + The subquery has already been executed (for real, it wasn't EXPLAIN's + fake execution) so it should not matter what it has inside. + + The actual reason for not walking inside is that parts of the subquery + (e.g. JTBM join nests and their IN-equality conditions may have been + invalidated by irreversible cleanups (those happen after an uncorrelated + subquery has been executed). + */ + return (this->*transformer)(thd, arg); + } + + /* + TODO(varun): this is needed for the sort-nest when we have dependent + subqueries, for such cases we would need to introduce a new + parameter to transform function like transform_subquery, + if set to TRUE we would change the inner contents of the + subquery also. + */ + if (transform_subquery) + { + for (SELECT_LEX *lex= unit->first_select(); lex; lex= lex->next_select()) + { + List_iterator<Item> it(lex->item_list); + Item *item, *new_item; + ORDER *order; + + if (lex->where) + { + lex->where= (lex->where)->transform(thd, transformer, TRUE, arg); + lex->where->update_used_tables(); + } + if (lex->having) + { + lex->having= (lex->having)->transform(thd, transformer, TRUE, arg); + lex->having->update_used_tables(); + } + + while ((item=it++)) + { + if ((new_item= item->transform(thd, transformer, TRUE, arg)) != item) + { + new_item->name= item->name; + thd->change_item_tree(it.ref(), new_item); + it.replace(new_item); + } + new_item->update_used_tables(); + } + + for (order= lex->order_list.first ; order; order= order->next) + { + *order->item= (*order->item)->transform(thd, transformer, + TRUE, arg); + (*order->item)->update_used_tables(); + } + + for (order= lex->group_list.first ; order; order= order->next) + { + *order->item= (*order->item)->transform(thd, transformer, TRUE, arg); + (*order->item)->update_used_tables(); + } + } + } + + return (this->*transformer)(thd, arg); +} + + bool Item_subselect::exec() { subselect_engine *org_engine= engine; diff --git a/sql/item_subselect.h b/sql/item_subselect.h index dc8417495c5..bf0f7617b36 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -234,6 +234,9 @@ public: virtual void reset_value_registration() {} enum_parsing_place place() { return parsing_place; } bool walk(Item_processor processor, bool walk_subquery, void *arg); + Item* transform(THD *thd, Item_transformer transformer, + bool transform_subquery, uchar *arg); + bool mark_as_eliminated_processor(void *arg); bool eliminate_subselect_processor(void *arg); bool set_fake_select_as_master_processor(void *arg); diff --git a/sql/opt_split.cc b/sql/opt_split.cc index cfac0c93544..1bdaa1e16ed 100644 --- a/sql/opt_split.cc +++ b/sql/opt_split.cc @@ -653,7 +653,7 @@ add_ext_keyuses_for_splitting_field(Dynamic_array<KEYUSE_EXT> *ext_keyuses, */ static -double spl_postjoin_oper_cost(THD *thd, double join_record_count, uint rec_len) +double spl_postjoin_oper_cost(THD *thd, double join_record_count, ulong rec_len) { double cost; cost= get_tmp_table_write_cost(thd, join_record_count,rec_len) * @@ -696,7 +696,7 @@ void JOIN::add_keyuses_for_splitting() KEYUSE_EXT *keyuse_ext; KEYUSE_EXT keyuse_ext_end; double oper_cost; - uint rec_len; + ulong rec_len; uint added_keyuse_count; TABLE *table= select_lex->master_unit()->derived->table; List_iterator_fast<KEY_FIELD> li(spl_opt_info->added_key_fields); @@ -971,7 +971,7 @@ SplM_plan_info * JOIN_TAB::choose_best_splitting(double record_count, (spl_opt_info->unsplit_card ? spl_opt_info->unsplit_card : 1); - uint rec_len= table->s->rec_buff_length; + ulong rec_len= table->s->rec_buff_length; double split_card= spl_opt_info->unsplit_card * spl_plan->split_sel; double oper_cost= split_card * diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index c7d95fdadec..430b131e50f 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -448,7 +448,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred); static bool convert_subq_to_jtbm(JOIN *parent_join, Item_in_subselect *subq_pred, bool *remove); static TABLE_LIST *alloc_join_nest(THD *thd); -static uint get_tmp_table_rec_length(Ref_ptr_array p_list, uint elements); +static ulong get_tmp_table_rec_length(Ref_ptr_array p_list, uint elements); bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables); static SJ_MATERIALIZATION_INFO * at_sjmat_pos(const JOIN *join, table_map remaining_tables, const JOIN_TAB *tab, @@ -2509,8 +2509,8 @@ bool optimize_semijoin_nests(JOIN *join, table_map all_table_map) /* Calculate temporary table parameters and usage costs */ - uint rowlen= get_tmp_table_rec_length(subq_select->ref_pointer_array, - subq_select->item_list.elements); + ulong rowlen= get_tmp_table_rec_length(subq_select->ref_pointer_array, + subq_select->item_list.elements); double lookup_cost= get_tmp_table_lookup_cost(join->thd, subjoin_out_rows, rowlen); double write_cost= get_tmp_table_write_cost(join->thd, @@ -2557,9 +2557,9 @@ bool optimize_semijoin_nests(JOIN *join, table_map all_table_map) Length of the temptable record, in bytes */ -static uint get_tmp_table_rec_length(Ref_ptr_array p_items, uint elements) +static ulong get_tmp_table_rec_length(Ref_ptr_array p_items, uint elements) { - uint len= 0; + ulong len= 0; Item *item; //List_iterator<Item> it(items); for (uint i= 0; i < elements ; i++) @@ -2610,7 +2610,7 @@ static uint get_tmp_table_rec_length(Ref_ptr_array p_items, uint elements) */ double -get_tmp_table_lookup_cost(THD *thd, double row_count, uint row_size) +get_tmp_table_lookup_cost(THD *thd, double row_count, ulong row_size) { if (row_count > thd->variables.max_heap_table_size / (double) row_size) return (double) DISK_TEMPTABLE_LOOKUP_COST; @@ -2630,7 +2630,7 @@ get_tmp_table_lookup_cost(THD *thd, double row_count, uint row_size) */ double -get_tmp_table_write_cost(THD *thd, double row_count, uint row_size) +get_tmp_table_write_cost(THD *thd, double row_count, ulong row_size) { double lookup_cost= get_tmp_table_lookup_cost(thd, row_count, row_size); /* @@ -3139,7 +3139,8 @@ bool Sj_materialization_picker::check_qep(JOIN *join, { best_access_path(join, join->positions[i].table, rem_tables, join->positions, i, - disable_jbuf, prefix_rec_count, &curpos, &dummy); + disable_jbuf, prefix_rec_count, &curpos, &dummy, + 0, FALSE); prefix_rec_count= COST_MULT(prefix_rec_count, curpos.records_read); prefix_cost= COST_ADD(prefix_cost, curpos.read_time); prefix_cost= COST_ADD(prefix_cost, @@ -3756,6 +3757,7 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) SJ_MATERIALIZATION_INFO *sjm= s->emb_sj_nest->sj_mat_info; sjm->is_used= TRUE; sjm->is_sj_scan= FALSE; + bool save_sort_nest_op= pos->sort_nest_operation_here; memcpy((uchar*) (pos - sjm->tables + 1), (uchar*) sjm->positions, sizeof(POSITION) * sjm->tables); recalculate_prefix_record_count(join, tablenr - sjm->tables + 1, @@ -3763,6 +3765,7 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) first= tablenr - sjm->tables + 1; join->best_positions[first].n_sj_tables= sjm->tables; join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE; + join->best_positions[first].sort_nest_operation_here= save_sort_nest_op; Json_writer_object semijoin_strategy(thd); semijoin_strategy.add("semi_join_strategy","SJ-Materialization"); Json_writer_array semijoin_plan(thd, "join_order"); @@ -3783,11 +3786,14 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) sjm->is_used= TRUE; sjm->is_sj_scan= TRUE; first= pos->sjmat_picker.sjm_scan_last_inner - sjm->tables + 1; + POSITION *last_inner= join->best_positions+ first + sjm->tables - 1; + bool save_sort_nest_op= last_inner->sort_nest_operation_here; memcpy((uchar*) (join->best_positions + first), (uchar*) sjm->positions, sizeof(POSITION) * sjm->tables); recalculate_prefix_record_count(join, first, first + sjm->tables); join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE_SCAN; join->best_positions[first].n_sj_tables= sjm->tables; + join->best_positions[first].sort_nest_operation_here= save_sort_nest_op; /* Do what advance_sj_state did: re-run best_access_path for every table in the [last_inner_table + 1; pos..) range @@ -3822,15 +3828,17 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) Json_writer_object trace_one_table(thd); trace_one_table.add_table_name(join->best_positions[i].table); } + save_sort_nest_op= join->best_positions[i].sort_nest_operation_here; best_access_path(join, join->best_positions[i].table, rem_tables, join->best_positions, i, FALSE, prefix_rec_count, - join->best_positions + i, &dummy); + join->best_positions + i, &dummy, 0, FALSE); + join->best_positions[i].sort_nest_operation_here= save_sort_nest_op; prefix_rec_count *= join->best_positions[i].records_read; rem_tables &= ~join->best_positions[i].table->table->map; } } - + if (pos->sj_strategy == SJ_OPT_FIRST_MATCH) { first= pos->firstmatch_picker.first_firstmatch_table; @@ -3866,7 +3874,8 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) best_access_path(join, join->best_positions[idx].table, rem_tables, join->best_positions, idx, TRUE /* no jbuf */, - record_count, join->best_positions + idx, &dummy); + record_count, join->best_positions + idx, &dummy, + 0, FALSE); } record_count *= join->best_positions[idx].records_read; rem_tables &= ~join->best_positions[idx].table->table->map; @@ -3906,7 +3915,7 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) rem_tables, join->best_positions, idx, TRUE /* no jbuf */, record_count, join->best_positions + idx, - &loose_scan_pos); + &loose_scan_pos, 0, FALSE); if (idx==first) { join->best_positions[idx]= loose_scan_pos; @@ -3949,7 +3958,10 @@ void fix_semijoin_strategies_for_picked_join_order(JOIN *join) for (uint i= first; i < i_end; i++) { if (i != first) + { join->best_positions[i].sj_strategy= SJ_OPT_NONE; + DBUG_ASSERT(!join->best_positions[i].sort_nest_operation_here); + } handled_tabs |= join->best_positions[i].table->table->map; } @@ -4025,7 +4037,14 @@ bool setup_sj_materialization_part1(JOIN_TAB *sjm_tab) DBUG_ENTER("setup_sj_materialization"); - /* Walk out of outer join nests until we reach the semi-join nest we're in */ + /* + Walk out of outer join nests until we reach the semi-join nest we're in. + There can be a case that the first [in join order ordering] table + inside semi-join-materialization nest is also an inner table wrt an + outer join (that is embedded in the semi-join). + This can happen when all of the tables that are inside the semi-join + but not inside the outer join are constant + */ while (!emb_sj_nest->sj_mat_info) emb_sj_nest= emb_sj_nest->embedding; @@ -4911,6 +4930,11 @@ int setup_semijoin_loosescan(JOIN *join) for (i= join->const_tables ; i < join->top_join_tab_count; ) { JOIN_TAB *tab=join->join_tab + i; + if (tab->is_sort_nest) + { + i++; + continue; + } switch (pos->sj_strategy) { case SJ_OPT_MATERIALIZE: case SJ_OPT_MATERIALIZE_SCAN: @@ -5058,11 +5082,20 @@ int setup_semijoin_dups_elimination(JOIN *join, ulonglong options, DBUG_ENTER("setup_semijoin_dups_elimination"); join->complex_firstmatch_tables= table_map(0); + Sort_nest_info *sort_nest_info= join->sort_nest_info; + + if (sort_nest_info) + no_jbuf_after= join->const_tables+ sort_nest_info->number_of_tables(); POSITION *pos= join->best_positions + join->const_tables; for (i= join->const_tables ; i < join->top_join_tab_count; ) { JOIN_TAB *tab=join->join_tab + i; + if (tab->is_sort_nest) + { + i++; + continue; + } switch (pos->sj_strategy) { case SJ_OPT_MATERIALIZE: case SJ_OPT_MATERIALIZE_SCAN: @@ -5651,6 +5684,36 @@ enum_nested_loop_state join_tab_execution_startup(JOIN_TAB *tab) sjm->materialized= TRUE; } } + else if (tab->is_sort_nest) + { + /* + This is where the sort-nest gets filled by the partial join. + This would compute the partial join and write the records + in the temporary table. + */ + + /* + TODO(varun): this can be move to the SJM nest when the handling + of sort-nest is done with a bush + */ + enum_nested_loop_state rc; + JOIN *join= tab->join; + Sort_nest_info *nest_info= join->sort_nest_info; + + if (!nest_info->is_materialized()) + { + JOIN_TAB *join_tab= join->join_tab + join->const_tables; + JOIN_TAB *save_return_tab= join->return_tab; + if ((rc= sub_select(join, join_tab, FALSE)) < 0 || + (rc= sub_select(join, join_tab, TRUE)) < 0) + { + join->return_tab= save_return_tab; + DBUG_RETURN(rc); + } + join->return_tab= save_return_tab; + nest_info->set_materialized(); + } + } DBUG_RETURN(NESTED_LOOP_OK); } @@ -6514,7 +6577,7 @@ bool JOIN::choose_subquery_plan(table_map join_tables) */ /* C.1 Compute the cost of the materialization strategy. */ //uint rowlen= get_tmp_table_rec_length(unit->first_select()->item_list); - uint rowlen= get_tmp_table_rec_length(ref_ptrs, + ulong rowlen= get_tmp_table_rec_length(ref_ptrs, select_lex->item_list.elements); /* The cost of writing one row into the temporary table. */ double write_cost= get_tmp_table_write_cost(thd, inner_record_count_1, @@ -7113,7 +7176,7 @@ bool Item_in_subselect::pushdown_cond_for_in_subquery(THD *thd, Item *cond) remaining_cond= remaining_cond->transform(thd, &Item::in_subq_field_transformer_for_having, - (uchar *)this); + FALSE, (uchar *)this); if (!remaining_cond || remaining_cond->walk(&Item::cleanup_excluding_const_fields_processor, 0, 0)) diff --git a/sql/opt_subselect.h b/sql/opt_subselect.h index abd37f1e98e..6ade22b3a65 100644 --- a/sql/opt_subselect.h +++ b/sql/opt_subselect.h @@ -302,6 +302,8 @@ public: pos->loosescan_picker.loosescan_key= best_loose_scan_key; pos->loosescan_picker.loosescan_parts= best_max_loose_keypart + 1; pos->use_join_buffer= FALSE; + pos->sort_nest_operation_here= FALSE; + pos->index_no= -1; pos->table= tab; pos->range_rowid_filter_info= tab->range_rowid_filter_info; pos->ref_depend_map= best_ref_depend_map; diff --git a/sql/opt_trace.cc b/sql/opt_trace.cc index d95f0795542..a6e2740ac17 100644 --- a/sql/opt_trace.cc +++ b/sql/opt_trace.cc @@ -615,6 +615,13 @@ void Json_writer::add_table_name(const JOIN_TAB *tab) ctab->emb_sj_nest->sj_subq_pred->get_identifier()); add_str(table_name_buffer, len); } + else if (tab->is_sort_nest) + { + size_t len= my_snprintf(table_name_buffer, + sizeof(table_name_buffer)-1, + "<sort-nest>"); + add_str(table_name_buffer, len); + } else { TABLE_LIST *real_table= tab->table->pos_in_table_list; @@ -707,6 +714,42 @@ void print_best_access_for_table(THD *thd, POSITION *pos, /* + Add the tables that are inside the sort-nest + in the optimizer trace +*/ +void add_sort_nest_tables_to_trace(JOIN *join, + Mat_join_tab_nest_info* nest_info) +{ + JOIN_TAB *end_tab, *tab; + THD *thd= join->thd; + end_tab= nest_info->nest_tab; + Json_writer_object trace_wrapper(thd); + Json_writer_array sort_nest(thd, "sort-nest"); + for (tab= join->join_tab + join->const_tables; tab < end_tab; tab++) + sort_nest.add_table_name(tab); +} + + +/* + This function is used during best_access_path to print the sort-nest + that were considered doing the cost based analysis of the various + join orders. +*/ + +void trace_sort_nest(JOIN *join, uint idx, table_map remaining_tables) +{ + THD *const thd= join->thd; + Json_writer_array plan_prefix(thd, "sort-nest"); + for (uint i= 0; i < idx; i++) + { + TABLE *tr= join->positions[i].table->table; + if (tr->map & remaining_tables) + plan_prefix.add_table_name(join->positions[i].table); + } +} + + +/* Introduce enum_query_type flags parameter, maybe also allow EXPLAIN also use this function. */ diff --git a/sql/opt_trace.h b/sql/opt_trace.h index 46adbec2c3c..d2330420a2e 100644 --- a/sql/opt_trace.h +++ b/sql/opt_trace.h @@ -109,6 +109,8 @@ void trace_plan_prefix(JOIN *join, uint idx, table_map join_tables); void print_final_join_order(JOIN *join); void print_best_access_for_table(THD *thd, POSITION *pos, enum join_type type); +void add_sort_nest_tables_to_trace(JOIN *join, + Mat_join_tab_nest_info* nest_info); /* Security related (need to add a proper comment here) diff --git a/sql/sql_class.h b/sql/sql_class.h index 0891cd214f8..7972ef0d83a 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -608,6 +608,7 @@ typedef struct system_variables ulonglong group_concat_max_len; ulonglong default_regex_flags; ulonglong max_mem_used; + my_bool use_sort_nest; /** Place holders to store Multi-source variables in sys_var.cc during diff --git a/sql/sql_const.h b/sql/sql_const.h index f7c820c727b..de8d5f9b596 100644 --- a/sql/sql_const.h +++ b/sql/sql_const.h @@ -296,6 +296,14 @@ */ #define MAX_TIME_ZONE_NAME_LENGTH (NAME_LEN + 1) + +/** + Average record length is the number of bytes for the record. + It is just a rough estimates which is needed to calculate + the cost of filling, reading and sorting the temporary table. +*/ +#define AVG_REC_LEN 50 + #if defined(__WIN__) #define INTERRUPT_PRIOR -2 diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index c713f1322dc..d416121adc5 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -1436,8 +1436,9 @@ bool pushdown_cond_for_derived(THD *thd, Item *cond, TABLE_LIST *derived) /* 1. Check what pushable formula can be extracted from cond */ Item *extracted_cond; - cond->check_pushable_cond(&Item::pushable_cond_checker_for_derived, - (uchar *)(&derived->table->map)); + cond->check_pushable_cond( + &Item::pushable_cond_checker_for_tables_with_equalities, + (uchar *)&derived->table->map); /* 2. Build a clone PC of the formula that can be extracted */ extracted_cond= cond->build_pushable_cond(thd, @@ -1493,7 +1494,7 @@ bool pushdown_cond_for_derived(THD *thd, Item *cond, TABLE_LIST *derived) remaining_cond= remaining_cond->transform(thd, &Item::derived_field_transformer_for_having, - (uchar *) sl); + FALSE, (uchar *) sl); if (!remaining_cond) continue; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 60b60ec1e54..10823ef1ecc 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3813,7 +3813,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) while ((item= li++)) { - item->transform(thd, &Item::update_value_transformer, + item->transform(thd, &Item::update_value_transformer, FALSE, (uchar*)lex->current_select); } } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index cbd2e953d7b..e1ed525c2e9 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -8469,178 +8469,6 @@ bool st_select_lex::collect_grouping_fields(THD *thd) } -/** - @brief - For a condition check possibility of exraction a formula over grouping fields - - @param thd The thread handle - @param cond The condition whose subformulas are to be analyzed - @param checker The checker callback function to be applied to the nodes - of the tree of the object - - @details - This method traverses the AND-OR condition cond and for each subformula of - the condition it checks whether it can be usable for the extraction of a - condition over the grouping fields of this select. The method uses - the call-back parameter checker to check whether a primary formula - depends only on grouping fields. - The subformulas that are not usable are marked with the flag NO_EXTRACTION_FL. - The subformulas that can be entierly extracted are marked with the flag - FULL_EXTRACTION_FL. - @note - This method is called before any call of extract_cond_for_grouping_fields. - The flag NO_EXTRACTION_FL set in a subformula allows to avoid building clone - for the subformula when extracting the pushable condition. - The flag FULL_EXTRACTION_FL allows to delete later all top level conjuncts - from cond. -*/ - -void -st_select_lex::check_cond_extraction_for_grouping_fields(THD *thd, Item *cond) -{ - if (cond->get_extraction_flag() == NO_EXTRACTION_FL) - return; - cond->clear_extraction_flag(); - if (cond->type() == Item::COND_ITEM) - { - Item_cond_and *and_cond= - (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) ? - ((Item_cond_and*) cond) : 0; - - List<Item> *arg_list= ((Item_cond*) cond)->argument_list(); - List_iterator<Item> li(*arg_list); - uint count= 0; // to count items not containing NO_EXTRACTION_FL - uint count_full= 0; // to count items with FULL_EXTRACTION_FL - Item *item; - while ((item=li++)) - { - check_cond_extraction_for_grouping_fields(thd, item); - if (item->get_extraction_flag() != NO_EXTRACTION_FL) - { - count++; - if (item->get_extraction_flag() == FULL_EXTRACTION_FL) - count_full++; - } - else if (!and_cond) - break; - } - if ((and_cond && count == 0) || item) - cond->set_extraction_flag(NO_EXTRACTION_FL); - if (count_full == arg_list->elements) - { - cond->set_extraction_flag(FULL_EXTRACTION_FL); - } - if (cond->get_extraction_flag() != 0) - { - li.rewind(); - while ((item=li++)) - item->clear_extraction_flag(); - } - } - else - { - int fl= cond->excl_dep_on_grouping_fields(this) && !cond->is_expensive() ? - FULL_EXTRACTION_FL : NO_EXTRACTION_FL; - cond->set_extraction_flag(fl); - } -} - - -/** - @brief - Build condition extractable from the given one depended on grouping fields - - @param thd The thread handle - @param cond The condition from which the condition depended - on grouping fields is to be extracted - @param no_top_clones If it's true then no clones for the top fully - extractable conjuncts are built - - @details - For the given condition cond this method finds out what condition depended - only on the grouping fields can be extracted from cond. If such condition C - exists the method builds the item for it. - This method uses the flags NO_EXTRACTION_FL and FULL_EXTRACTION_FL set by the - preliminary call of st_select_lex::check_cond_extraction_for_grouping_fields - to figure out whether a subformula depends only on these fields or not. - @note - The built condition C is always implied by the condition cond - (cond => C). The method tries to build the least restictive such - condition (i.e. for any other condition C' such that cond => C' - we have C => C'). - @note - The build item is not ready for usage: substitution for the field items - has to be done and it has to be re-fixed. - - @retval - the built condition depended only on grouping fields if such a condition exists - NULL if there is no such a condition -*/ - -Item *st_select_lex::build_cond_for_grouping_fields(THD *thd, Item *cond, - bool no_top_clones) -{ - if (cond->get_extraction_flag() == FULL_EXTRACTION_FL) - { - if (no_top_clones) - return cond; - cond->clear_extraction_flag(); - return cond->build_clone(thd); - } - if (cond->type() == Item::COND_ITEM) - { - bool cond_and= false; - Item_cond *new_cond; - if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC) - { - cond_and= true; - new_cond= new (thd->mem_root) Item_cond_and(thd); - } - else - new_cond= new (thd->mem_root) Item_cond_or(thd); - if (unlikely(!new_cond)) - return 0; - List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); - Item *item; - while ((item=li++)) - { - if (item->get_extraction_flag() == NO_EXTRACTION_FL) - { - DBUG_ASSERT(cond_and); - item->clear_extraction_flag(); - continue; - } - Item *fix= build_cond_for_grouping_fields(thd, item, - no_top_clones & cond_and); - if (unlikely(!fix)) - { - if (cond_and) - continue; - break; - } - new_cond->argument_list()->push_back(fix, thd->mem_root); - } - - if (!cond_and && item) - { - while((item= li++)) - item->clear_extraction_flag(); - return 0; - } - switch (new_cond->argument_list()->elements) - { - case 0: - return 0; - case 1: - return new_cond->argument_list()->head(); - default: - return new_cond; - } - } - return 0; -} - - bool st_select_lex::set_nest_level(int new_nest_level) { DBUG_ENTER("st_select_lex::set_nest_level"); @@ -10169,12 +9997,13 @@ void st_select_lex::pushdown_cond_into_where_clause(THD *thd, Item *cond, if (have_window_funcs()) { Item *cond_over_partition_fields; - check_cond_extraction_for_grouping_fields(thd, cond); - cond_over_partition_fields= - build_cond_for_grouping_fields(thd, cond, true); + cond->check_pushable_cond_extraction(&Item::pushable_cond_checker_for_grouping_fields, + (uchar*)this); + cond_over_partition_fields= cond->build_pushable_condition(thd, true); if (cond_over_partition_fields) cond_over_partition_fields= cond_over_partition_fields->transform(thd, &Item::grouping_field_transformer_for_where, + FALSE, (uchar*) this); if (cond_over_partition_fields) { @@ -10189,7 +10018,7 @@ void st_select_lex::pushdown_cond_into_where_clause(THD *thd, Item *cond, if (!join->group_list && !with_sum_func) { cond= - cond->transform(thd, transformer, arg); + cond->transform(thd, transformer, FALSE, arg); if (cond) { cond->walk( @@ -10205,9 +10034,10 @@ void st_select_lex::pushdown_cond_into_where_clause(THD *thd, Item *cond, the WHERE clause of this select. */ Item *cond_over_grouping_fields; - check_cond_extraction_for_grouping_fields(thd, cond); - cond_over_grouping_fields= - build_cond_for_grouping_fields(thd, cond, true); + cond->check_pushable_cond_extraction(&Item::pushable_cond_checker_for_grouping_fields, + (uchar*)this); + + cond_over_grouping_fields= cond->build_pushable_condition(thd, true); /* Transform references to the columns of condition that can be pushed @@ -10216,6 +10046,7 @@ void st_select_lex::pushdown_cond_into_where_clause(THD *thd, Item *cond, if (cond_over_grouping_fields) cond_over_grouping_fields= cond_over_grouping_fields->transform(thd, &Item::grouping_field_transformer_for_where, + FALSE, (uchar*) this); if (cond_over_grouping_fields) @@ -10383,7 +10214,7 @@ st_select_lex::build_pushable_cond_for_having_pushdown(THD *thd, Item *cond) { Item *result= cond->transform(thd, &Item::multiple_equality_transformer, - (uchar *)this); + FALSE, (uchar *)this); if (!result) return true; if (result->type() == Item::COND_ITEM && @@ -10436,7 +10267,7 @@ st_select_lex::build_pushable_cond_for_having_pushdown(THD *thd, Item *cond) { Item *result= item->transform(thd, &Item::multiple_equality_transformer, - (uchar *)item); + FALSE, (uchar *)item); if (!result) return true; if (result->type() == Item::COND_ITEM && @@ -10702,7 +10533,8 @@ Item *st_select_lex::pushdown_from_having_into_where(THD *thd, Item *having) */ List_iterator_fast<Item> it(attach_to_conds); Item *item; - check_cond_extraction_for_grouping_fields(thd, having); + having->check_pushable_cond_extraction(&Item::pushable_cond_checker_for_grouping_fields, + (uchar*)this); if (build_pushable_cond_for_having_pushdown(thd, having)) { attach_to_conds.empty(); @@ -10762,7 +10594,7 @@ Item *st_select_lex::pushdown_from_having_into_where(THD *thd, Item *having) { item= item->transform(thd, &Item::field_transformer_for_having_pushdown, - (uchar *)this); + FALSE, (uchar *)this); if (item->walk(&Item::cleanup_excluding_immutables_processor, 0, STOP_PTR) || item->fix_fields(thd, NULL)) diff --git a/sql/sql_lex.h b/sql/sql_lex.h index ad1bd6adfa2..90d1ec6934f 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1533,9 +1533,6 @@ public: void collect_grouping_fields_for_derived(THD *thd, ORDER *grouping_list); bool collect_grouping_fields(THD *thd); bool collect_fields_equal_to_grouping(THD *thd); - void check_cond_extraction_for_grouping_fields(THD *thd, Item *cond); - Item *build_cond_for_grouping_fields(THD *thd, Item *cond, - bool no_to_clones); List<Window_spec> window_specs; void prepare_add_window_spec(THD *thd); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 09a15aaf7df..06b00b992c4 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -116,7 +116,10 @@ static bool best_extension_by_limited_search(JOIN *join, uint idx, double record_count, double read_time, uint depth, uint prune_level, - uint use_cond_selectivity); + uint use_cond_selectivity, + table_map sort_nest_tables, + bool nest_created, + bool limit_applied_to_nest); static uint determine_search_depth(JOIN* join); C_MODE_START static int join_tab_cmp(const void *dummy, const void* ptr1, const void* ptr2); @@ -124,6 +127,8 @@ static int join_tab_cmp_straight(const void *dummy, const void* ptr1, const void static int join_tab_cmp_embedded_first(const void *emb, const void* ptr1, const void *ptr2); C_MODE_END static uint cache_record_length(JOIN *join,uint index); +static ulong cache_record_length_for_nest(JOIN *join,uint index); + static store_key *get_store_key(THD *thd, KEYUSE *keyuse, table_map used_tables, KEY_PART_INFO *key_part, uchar *key_buff, @@ -151,11 +156,11 @@ static COND *build_equal_items(JOIN *join, COND *cond, bool ignore_on_conds, COND_EQUAL **cond_equal_ref, bool link_equal_fields= FALSE); -static COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, - COND *cond, - COND_EQUAL *cond_equal, - void *table_join_idx, - bool do_substitution); +COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, + COND *cond, + COND_EQUAL *cond_equal, + void *table_join_idx, + bool do_substitution); static COND *simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top, bool in_sj); static bool check_interleaving_with_nj(JOIN_TAB *next); @@ -184,6 +189,8 @@ static enum_nested_loop_state end_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); static enum_nested_loop_state end_unique_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); +enum_nested_loop_state +end_nest_materialization(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); static int join_read_const_table(THD *thd, JOIN_TAB *tab, POSITION *pos); static int join_read_system(JOIN_TAB *tab); @@ -231,9 +238,9 @@ static bool test_if_cheaper_ordering(const JOIN_TAB *tab, ha_rows *new_select_limit, uint *new_used_key_parts= NULL, uint *saved_best_key_parts= NULL); -static int test_if_order_by_key(JOIN *join, - ORDER *order, TABLE *table, uint idx, - uint *used_key_parts= NULL); +int test_if_order_by_key(JOIN *join, + ORDER *order, TABLE *table, uint idx, + uint *used_key_parts= NULL); static bool test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order, ha_rows select_limit, bool no_changes, const key_map *map); @@ -298,7 +305,13 @@ static double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s, table_map rem_tables); void set_postjoin_aggr_write_func(JOIN_TAB *tab); -static Item **get_sargable_cond(JOIN *join, TABLE *table); +Item **get_sargable_cond(JOIN *join, TABLE *table); +void find_cost_of_index_with_ordering(THD *thd, const JOIN_TAB *tab, + TABLE *table, + ha_rows *select_limit_arg, + double fanout, double est_best_records, + uint nr, double *index_scan_time, + Json_writer_object *trace_possible_key); static bool build_notnull_conds_for_range_scans(JOIN *join, COND *cond, @@ -2531,6 +2544,14 @@ int JOIN::optimize_stage2() if (setup_semijoin_loosescan(this)) DBUG_RETURN(1); + if (sort_nest_needed()) + { + if (make_sort_nest(sort_nest_info)) + DBUG_RETURN(1); + substitute_best_fields_for_order_by_items(); + substitute_base_with_nest_field_items(sort_nest_info); + } + if (make_join_select(this, select, conds)) { zero_result_cause= @@ -2543,22 +2564,9 @@ int JOIN::optimize_stage2() error= -1; /* if goto err */ /* Optimize distinct away if possible */ - { - ORDER *org_order= order; - order=remove_const(this, order,conds,1, &simple_order); - if (unlikely(thd->is_error())) - { - error= 1; - DBUG_RETURN(1); - } + if (remove_const_from_order_by()) + DBUG_RETURN(TRUE); - /* - If we are using ORDER BY NULL or ORDER BY const_expression, - return result in any order (even if we are using a GROUP BY) - */ - if (!order && org_order) - skip_sort_order= 1; - } /* Check if we can optimize away GROUP BY/DISTINCT. We can do that if there are no aggregate functions, the @@ -2779,7 +2787,8 @@ int JOIN::optimize_stage2() Yet the current implementation of FORCE INDEX hints does not allow us to do it in a clean manner. */ - no_jbuf_after= 1 ? table_count : make_join_orderinfo(this); + no_jbuf_after= 1 ? table_count + : make_join_orderinfo(this); // Don't use join buffering when we use MATCH select_opts_for_readinfo= @@ -2852,19 +2861,10 @@ int JOIN::optimize_stage2() if (order && !need_tmp) { - /* - Force using of tmp table if sorting by a SP or UDF function due to - their expensive and probably non-deterministic nature. - */ - for (ORDER *tmp_order= order; tmp_order ; tmp_order=tmp_order->next) + if (is_order_by_expensive()) { - Item *item= *tmp_order->item; - if (item->is_expensive()) - { - /* Force tmp table without sort */ - need_tmp=1; simple_order=simple_group=0; - break; - } + need_tmp=1; + simple_order=simple_group=0; } } @@ -2950,10 +2950,25 @@ int JOIN::optimize_stage2() else if (order && // ORDER BY wo/ preceding GROUP BY (simple_order || skip_sort_order)) // which is possibly skippable { - if (test_if_skip_sort_order(tab, order, select_limit, false, - &tab->table->keys_in_use_for_order_by)) + if (!sort_nest_info) { - ordered_index_usage= ordered_index_order_by; + if (test_if_skip_sort_order(tab, order, select_limit, false, + &tab->table->keys_in_use_for_order_by)) + { + ordered_index_usage= ordered_index_order_by; + } + } + else + { + JOIN_TAB *first_tab= sort_nest_info->nest_tab; + int idx= first_tab->get_index_on_table(); + + if (first_tab == join_tab + const_tables && + first_tab->check_if_index_satisfies_ordering(idx)) + { + resetup_access_for_ordering(first_tab, idx); + ordered_index_usage= ordered_index_order_by; + } } } } @@ -3653,7 +3668,9 @@ bool JOIN::make_aggr_tables_info() curr_tab->type != JT_EQ_REF) // Don't sort 1 row { // Sort either first non-const table or the last tmp table - JOIN_TAB *sort_tab= curr_tab; + JOIN_TAB *sort_tab= sort_nest_info ? + sort_nest_info->nest_tab : + curr_tab; if (add_sorting_to_table(sort_tab, order_arg)) DBUG_RETURN(true); @@ -3911,7 +3928,7 @@ bool JOIN::setup_subquery_caches() JOIN_TAB *tab; if (conds && !(conds= conds->transform(thd, &Item::expr_cache_insert_transformer, - NULL))) + FALSE, NULL))) DBUG_RETURN(TRUE); for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab; tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS)) @@ -3920,20 +3937,20 @@ bool JOIN::setup_subquery_caches() !(tab->select_cond= tab->select_cond->transform(thd, &Item::expr_cache_insert_transformer, - NULL))) + FALSE, NULL))) DBUG_RETURN(TRUE); if (tab->cache_select && tab->cache_select->cond) if (!(tab->cache_select->cond= tab->cache_select-> cond->transform(thd, &Item::expr_cache_insert_transformer, - NULL))) + FALSE, NULL))) DBUG_RETURN(TRUE); } if (having && !(having= having->transform(thd, &Item::expr_cache_insert_transformer, - NULL))) + FALSE, NULL))) DBUG_RETURN(TRUE); if (tmp_having) @@ -3942,7 +3959,7 @@ bool JOIN::setup_subquery_caches() if (!(tmp_having= tmp_having->transform(thd, &Item::expr_cache_insert_transformer, - NULL))) + FALSE, NULL))) DBUG_RETURN(TRUE); } } @@ -3957,7 +3974,7 @@ bool JOIN::setup_subquery_caches() Item *new_item; if (!(new_item= item->transform(thd, &Item::expr_cache_insert_transformer, - NULL))) + FALSE, NULL))) DBUG_RETURN(TRUE); if (new_item != item) { @@ -3969,7 +3986,7 @@ bool JOIN::setup_subquery_caches() if (!(*tmp_group->item= (*tmp_group->item)->transform(thd, &Item::expr_cache_insert_transformer, - NULL))) + FALSE, NULL))) DBUG_RETURN(TRUE); } } @@ -3980,7 +3997,7 @@ bool JOIN::setup_subquery_caches() if (!(*ord->item= (*ord->item)->transform(thd, &Item::expr_cache_insert_transformer, - NULL))) + FALSE, NULL))) DBUG_RETURN(TRUE); } } @@ -4488,7 +4505,7 @@ JOIN::destroy() WITH_CONST_TABLES); tab; tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS)) { - if (tab->aggr) + if (tab->aggr || tab->is_sort_nest) { free_tmp_table(thd, tab->table); delete tab->tmp_table_param; @@ -4785,7 +4802,7 @@ void mark_join_nest_as_const(JOIN *join, - "t1 LEFT JOIN (...) ON ..." uses the join nest's ON expression. */ -static Item **get_sargable_cond(JOIN *join, TABLE *table) +Item **get_sargable_cond(JOIN *join, TABLE *table) { Item **retval; if (table->pos_in_table_list->on_expr) @@ -5340,6 +5357,19 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, join->join_tab= stat; join->make_notnull_conds_for_range_scans(); + /* + Here a call is made to remove the constant from the order by clause, + this call would only remove the basic constants. This is done primarily + for the ORDER BY LIMIT optimization with the sort-nest. + */ + + if (join->remove_const_from_order_by()) + DBUG_RETURN(TRUE); + + if (join->sort_nest_allowed() && !join->is_order_by_expensive()) + join->sort_nest_possible= TRUE; + + join->propagate_equal_field_for_orderby(); /* Calc how many (possible) matched records in each table */ @@ -5399,6 +5429,8 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, all select distinct fields participate in one index. */ add_group_and_distinct_keys(join, s); + s->find_keys_that_can_achieve_ordering(); + s->get_estimated_record_length(); s->table->cond_selectivity= 1.0; @@ -5505,13 +5537,14 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, if (pull_out_semijoin_tables(join)) DBUG_RETURN(TRUE); - join->join_tab=stat; join->top_join_tab_count= table_count; - join->map2table=stat_ref; - join->table= table_vector; join->const_tables=const_count; join->found_const_table_map=found_const_table_map; + join->join_tab=stat; + join->map2table=stat_ref; + join->table= table_vector; + if (join->const_tables != join->table_count) optimize_keyuse(join, keyuse_array); @@ -7146,6 +7179,7 @@ void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key) join->positions[idx].sj_strategy= SJ_OPT_NONE; join->positions[idx].use_join_buffer= FALSE; join->positions[idx].range_rowid_filter_info= 0; + join->positions[idx].sort_nest_operation_here= FALSE; /* Move the const table as down as possible in best_ref */ JOIN_TAB **pos=join->best_ref+idx+1; @@ -7241,7 +7275,10 @@ double matching_candidates_in_table(JOIN_TAB *s, bool with_found_constraint, @param pos OUT Table access plan @param loose_scan_pos OUT Table plan that uses loosescan, or set cost to DBL_MAX if not possible. - + @param cardinality contains the estimate of records for the best + join order (if the join optimizer is run once + to get the best cardinality else it is set to + DBL_MAX) @return None */ @@ -7255,7 +7292,8 @@ best_access_path(JOIN *join, bool disable_jbuf, double record_count, POSITION *pos, - POSITION *loose_scan_pos) + POSITION *loose_scan_pos, + table_map sort_nest_tables, bool nest_created) { THD *thd= join->thd; uint use_cond_selectivity= thd->variables.optimizer_use_condition_selectivity; @@ -7276,6 +7314,8 @@ best_access_path(JOIN *join, Range_rowid_filter_cost_info *filter= 0; const char* cause= NULL; enum join_type best_type= JT_UNKNOWN, type= JT_UNKNOWN; + int index_picked= -1; + int index_used_for_access= -1; disable_jbuf= disable_jbuf || idx == join->const_tables; @@ -7372,7 +7412,11 @@ best_access_path(JOIN *join, notnull_part|=keyuse->keypart_map; double tmp2= prev_record_reads(join_positions, idx, - (found_ref | keyuse->used_tables)); + (found_ref | keyuse->used_tables), + sort_nest_tables, + nest_created ? + join->fraction_output_for_nest : + 1.0); if (tmp2 < best_prev_record_reads) { best_part_found_ref= keyuse->used_tables & ~join->const_table_map; @@ -7413,7 +7457,10 @@ best_access_path(JOIN *join, Really, there should be records=0.0 (yes!) but 1.0 would be probably safer */ - tmp= prev_record_reads(join_positions, idx, found_ref); + tmp= prev_record_reads(join_positions, idx, found_ref, + sort_nest_tables, + nest_created ? + join->fraction_output_for_nest : 1.0); records= 1.0; type= JT_FT; trace_access_idx.add("access_type", join_type_str[type]) @@ -7442,7 +7489,10 @@ best_access_path(JOIN *join, type= JT_EQ_REF; trace_access_idx.add("access_type", join_type_str[type]) .add("index", keyinfo->name); - tmp = prev_record_reads(join_positions, idx, found_ref); + tmp = prev_record_reads(join_positions, idx, found_ref, + sort_nest_tables, + nest_created ? + join->fraction_output_for_nest : 1.0); records=1.0; } else @@ -7749,12 +7799,75 @@ best_access_path(JOIN *join, DBUG_ASSERT(tmp >= 0); } } + + /* + Cost for sorting is added for the cases when ref access does + not satisfy the ORDER BY clause + An example would be + query + select * from t1 where t1.a=5 and c=2 order by b; + + we have 2 keys idx1(a,b) and idx2(a,c) + so cost of sorting needs to be added for idx2 but not for idx1 + as it can resolve the ORDER BY clause + */ + double cost_of_sorting= 0; + if (join->is_index_with_ordering_allowed(idx)) + { + if (!s->check_if_index_satisfies_ordering(start_key->key)) + { + double sort_cost; + sort_cost= join->sort_nest_oper_cost(records, idx, + s->get_estimated_record_length()); + cost_of_sorting= sort_cost; + if (unlikely(thd->trace_started())) + { + trace_access_idx.add("cost_of_sorting", cost_of_sorting); + trace_access_idx.add("satisfies_ordering", false); + } + } + else + { + /* + Apply limit here if possible, as ref access here achieves the + key that would satisfy the ordering. + */ + double records_before_limit_applied= records; + records= COST_MULT(records, join->fraction_output_for_nest); + if (unlikely(thd->trace_started())) + { + trace_access_idx.add("records_before_limit_considered", + records_before_limit_applied); + trace_access_idx.add("records_after_limit_considered", + floor(records)); + trace_access_idx.add("satisfies_ordering", true); + } + tmp= records; + set_if_smaller(tmp, (double) thd->variables.max_seeks_for_key); + if (table->covering_keys.is_set(key)) + tmp= table->file->keyread_time(key, 1, (ha_rows) tmp); + else + tmp= table->file->read_time(key, 1, + (ha_rows) MY_MIN(tmp,s->worst_seeks)); + tmp= COST_MULT(tmp, record_count); + } + } + trace_access_idx.add("rows", records).add("cost", tmp); - if (tmp + 0.0001 < best_time - records/(double) TIME_FOR_COMPARE) + if (tmp + cost_of_sorting + 0.0001 < (best_time - + records/(double) TIME_FOR_COMPARE)) { trace_access_idx.add("chosen", true); - best_time= COST_ADD(tmp, records/(double) TIME_FOR_COMPARE); + best_time= COST_ADD(COST_ADD(tmp, cost_of_sorting), + records/(double) TIME_FOR_COMPARE); + /* + best here is assigned the cost of reading the rows via the index + and not the cost of evalutation the part of the where clause + satisfied by this ref access. This is done so because we add the + cost of evaluating the where clause in + best_extension_by_limited_search. + */ best= tmp; best_records= records; best_key= start_key; @@ -7823,6 +7936,8 @@ best_access_path(JOIN *join, trace_access_hash.add("chosen", true); } + index_used_for_access= best_key ? best_key->key : -1; + index_picked= index_used_for_access; /* Don't test table scan if it can't be better. Prefer key lookup if we would use the same key for scanning. @@ -7897,6 +8012,7 @@ best_access_path(JOIN *join, double rows= record_count * s->found_records; double access_cost_factor= MY_MIN(tmp / rows, 1.0); uint key_no= s->quick->index; + index_used_for_access= key_no; filter= s->table->best_range_rowid_filter_for_partial_join(key_no, rows, access_cost_factor); @@ -7921,11 +8037,13 @@ best_access_path(JOIN *join, { type= JT_NEXT; tmp= s->table->file->read_time(s->ref.key, 1, s->records); + index_used_for_access= s->ref.key; } else // table scan { tmp= s->scan_time(); type= JT_ALL; + index_used_for_access= -1; } if ((s->table->map & join->outer_join) || disable_jbuf) // Can't use join cache @@ -7999,6 +8117,7 @@ best_access_path(JOIN *join, join->outer_join))); spl_plan= 0; best_type= type; + index_picked= index_used_for_access; } trace_access_scan.add("chosen", best_key == NULL); } @@ -8009,18 +8128,14 @@ best_access_path(JOIN *join, trace_access_scan.add("cause", "cost"); } - /* Update the cost information for the current partial plan */ - pos->records_read= records; - pos->read_time= best; - pos->key= best_key; - pos->table= s; - pos->ref_depend_map= best_ref_depends_map; - pos->loosescan_picker.loosescan_key= MAX_KEY; - pos->use_join_buffer= best_uses_jbuf; - pos->spl_plan= spl_plan; - pos->range_rowid_filter_info= best_filter; - - loose_scan_opt.save_to_position(s, loose_scan_pos); + if (!best_key && + idx == join->const_tables && + s->table == join->sort_by_table && + join->unit->select_limit_cnt >= records) + { + trace_access_scan.add("use_tmp_table", true); + join->sort_by_table= (TABLE*) 1; // Must use temporary table + } if (!best_key && idx == join->const_tables && @@ -8033,6 +8148,76 @@ best_access_path(JOIN *join, trace_access_scan.end(); trace_paths.end(); + /* + Use the estimate of rows read for a table for range/table scan + from TABLE::quick_condition_rows. This is due to the reason + records already take into account condition selectivity for the table + for range/table scan. + */ + + double idx_records= best_key ? records : s->table->quick_condition_rows; + double idx_time= best; + if (join->is_index_with_ordering_allowed(idx)) + { + if (s->check_if_index_satisfies_ordering(index_picked)) + { + /* + Removing the selectivity of limit taken into account for an access + which could resolve the ORDER BY clause by using an index. + This is done because we apply the selectivity again in the function + get_best_index_for_order_by_limit, so we make sure that the + selectivity is not applied twice. + */ + idx_records= MY_MIN(s->table->stat_records(), + COST_MULT(idx_records, + 1 / join->fraction_output_for_nest)); + } + else + { + /* + Also adding here the cost of sorting for best access method that cannot + resolve the ordering. This is done so that the cost comparison is more + accurate when we try to pick an index that can resolve the ordering + in the function get_best_index_for_order_by_limit(). + */ + double sort_cost; + sort_cost= join->sort_nest_oper_cost(idx_records, idx, + s->get_estimated_record_length()); + idx_time+= sort_cost; + } + } + int idx_no= get_best_index_for_order_by_limit(s, join->select_limit, + &idx_time, + &idx_records, + index_picked, idx); + + if (idx_no >= 0) + { + best= idx_time; + best_key= 0; + best_ref_depends_map= 0; + best_filter= 0; + spl_plan= 0; + records= idx_records; + index_picked= idx_no; + } + + + /* Update the cost information for the current partial plan */ + pos->records_read= records; + pos->read_time= best; + pos->key= best_key; + pos->table= s; + pos->ref_depend_map= best_ref_depends_map; + pos->loosescan_picker.loosescan_key= MAX_KEY; + pos->use_join_buffer= best_uses_jbuf; + pos->spl_plan= spl_plan; + pos->range_rowid_filter_info= best_filter; + pos->sort_nest_operation_here= FALSE; + pos->index_no= index_picked; + + loose_scan_opt.save_to_position(s, loose_scan_pos); + if (unlikely(thd->trace_started())) print_best_access_for_table(thd, pos, best_type); @@ -8233,10 +8418,16 @@ choose_plan(JOIN *join, table_map join_tables) if (search_depth == 0) /* Automatically determine a reasonable value for 'search_depth' */ search_depth= determine_search_depth(join); + + if (join->sort_nest_possible && + join->estimate_cardinality_for_join(join_tables)) + DBUG_RETURN(TRUE); + if (greedy_search(join, join_tables, search_depth, prune_level, use_cond_selectivity)) DBUG_RETURN(TRUE); } + trace_plan.end(); /* Store the cost of this query into a user variable @@ -8524,7 +8715,8 @@ optimize_straight_join(JOIN *join, table_map join_tables) /* Find the best access method from 's' to the current partial plan */ best_access_path(join, s, join_tables, join->positions, idx, disable_jbuf, record_count, - position, &loose_scan_pos); + position, &loose_scan_pos, + 0, FALSE); /* compute the cost of the new plan extended with 's' */ record_count= COST_MULT(record_count, position->records_read); @@ -8543,6 +8735,7 @@ optimize_straight_join(JOIN *join, table_map join_tables) pushdown_cond_selectivity= table_cond_selectivity(join, idx, s, join_tables); position->cond_selectivity= pushdown_cond_selectivity; + record_count= record_count*pushdown_cond_selectivity; ++idx; } @@ -8655,6 +8848,9 @@ greedy_search(JOIN *join, JOIN_TAB *best_table; // the next plan node to be added to the curr QEP // ==join->tables or # tables in the sj-mat nest we're optimizing uint n_tables __attribute__((unused)); + + join->set_fraction_output_for_nest(); + DBUG_ENTER("greedy_search"); /* number of tables that remain to be optimized */ @@ -8668,9 +8864,15 @@ greedy_search(JOIN *join, do { /* Find the extension of the current QEP with the lowest cost */ join->best_read= DBL_MAX; - if (best_extension_by_limited_search(join, remaining_tables, idx, record_count, - read_time, search_depth, prune_level, - use_cond_selectivity)) + table_map sort_nest_tables= 0; + if (best_extension_by_limited_search(join, remaining_tables, idx, + record_count, read_time, + search_depth, + (join->sort_nest_possible ? 0 : + prune_level), + use_cond_selectivity, + sort_nest_tables, FALSE, + FALSE)) DBUG_RETURN(TRUE); /* 'best_read < DBL_MAX' means that optimizer managed to find @@ -9389,6 +9591,15 @@ double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s, (values: 0 = EXHAUSTIVE, 1 = PRUNE_BY_TIME_OR_ROWS) @param use_cond_selectivity specifies how the selectivity of the conditions pushed to a table should be taken into account + @param sort_nest_tables set of tables that satify the ORDER BY clause + If ORDER BY clause is not satisfield it contains + set of all tables in the prefix + @param nest_created set to TRUE if a prefix join order satisfied + the ORDER BY clause and we can add a sort-nest + on the found prefix + @param limit_applied_to_nest TRUE if limit is applied for the sort-nest + cost calculation + FALSE otherwise @retval FALSE ok @@ -9404,7 +9615,10 @@ best_extension_by_limited_search(JOIN *join, double read_time, uint search_depth, uint prune_level, - uint use_cond_selectivity) + uint use_cond_selectivity, + table_map sort_nest_tables, + bool nest_created, + bool limit_applied_to_nest) { DBUG_ENTER("best_extension_by_limited_search"); @@ -9430,7 +9644,24 @@ best_extension_by_limited_search(JOIN *join, JOIN_TAB *s; double best_record_count= DBL_MAX; double best_read_time= DBL_MAX; - bool disable_jbuf= join->thd->variables.join_cache_level == 0; + bool disable_jbuf= (join->thd->variables.join_cache_level == 0) || + nest_created; + bool save_prefix_resolves_ordering= join->prefix_resolves_ordering; + + if (nest_created && !limit_applied_to_nest) + { + double original_record_count= record_count; + record_count= COST_MULT(record_count, join->fraction_output_for_nest); + limit_applied_to_nest= TRUE; + + if (unlikely(thd->trace_started())) + { + Json_writer_object apply_limit(thd); + apply_limit.add("original_record_count", original_record_count); + apply_limit.add("record_count_after_limit_applied", record_count); + } + + } DBUG_EXECUTE("opt", print_plan(join, idx, record_count, read_time, read_time, "part_plan");); @@ -9443,6 +9674,7 @@ best_extension_by_limited_search(JOIN *join, if (join->emb_sjm_nest) allowed_tables= join->emb_sjm_nest->sj_inner_tables & ~join->const_table_map; + int index_used; for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++) { table_map real_table_bit= s->table->map; @@ -9459,12 +9691,31 @@ best_extension_by_limited_search(JOIN *join, { trace_plan_prefix(join, idx, remaining_tables); trace_one_table.add_table_name(s); + if (nest_created) + trace_sort_nest(join, idx, sort_nest_tables); } /* Find the best access method from 's' to the current partial plan */ POSITION loose_scan_pos; best_access_path(join, s, remaining_tables, join->positions, idx, - disable_jbuf, record_count, position, &loose_scan_pos); + disable_jbuf, record_count, position, &loose_scan_pos, + sort_nest_tables, nest_created); + + /* + sort_nest_operation_here is set to TRUE here in the special case + when we only have one table in the join. Generally + sort_nest_operation_here is set when we check if ORDER BY clause is + satisfied by the partial join order, in the sort-nest branch of + best_extension_by_limited_search. + */ + index_used= position->index_no; + if ((join->table_count - join->const_tables == 1) && + join->is_index_with_ordering_allowed(idx) && + s->check_if_index_satisfies_ordering(index_used)) + { + position->sort_nest_operation_here= TRUE; + } +>>>>>>> ORDER BY LIMIT /* Compute the cost of extending the plan with 's' */ current_record_count= COST_MULT(record_count, position->records_read); @@ -9472,10 +9723,10 @@ best_extension_by_limited_search(JOIN *join, ? position->range_rowid_filter_info->get_cmp_gain(current_record_count) : 0; current_read_time=COST_ADD(read_time, - COST_ADD(position->read_time - - filter_cmp_gain, - current_record_count / - (double) TIME_FOR_COMPARE)); + COST_ADD(position->read_time - + filter_cmp_gain, + current_record_count / + (double) TIME_FOR_COMPARE)); if (unlikely(thd->trace_started())) { @@ -9547,8 +9798,51 @@ best_extension_by_limited_search(JOIN *join, double partial_join_cardinality= current_record_count * pushdown_cond_selectivity; if ( (search_depth > 1) && (remaining_tables & ~real_table_bit) & allowed_tables ) - { /* Recursively expand the current partial plan */ + { + /* Recursively expand the current partial plan */ swap_variables(JOIN_TAB*, join->best_ref[idx], *pos); + + if (join->is_index_with_ordering_allowed(idx) && + s->check_if_index_satisfies_ordering(index_used)) + limit_applied_to_nest= TRUE; + + /* + TODO varun: + 1) get an optimizer switch to enable or disable the sort + nest instead of a system variable + */ + if (!nest_created && + (join->prefix_resolves_ordering || + join->consider_adding_sort_nest(sort_nest_tables | + real_table_bit))) + { + // SORT_NEST branch + join->positions[idx].sort_nest_operation_here= TRUE; + join->prefix_resolves_ordering= TRUE; + double sorting_cost= 0; + if (s->needs_filesort(idx, index_used)) + { + ulong rec_len= cache_record_length_for_nest(join, idx); + sorting_cost= join->sort_nest_oper_cost(partial_join_cardinality, + idx, rec_len); + trace_one_table.add("cost_of_sorting", sorting_cost); + } + Json_writer_array trace_rest(thd, "rest_of_plan"); + if (best_extension_by_limited_search(join, + remaining_tables & ~real_table_bit, + idx + 1, + partial_join_cardinality, + COST_ADD(current_read_time, + sorting_cost), + search_depth - 1, + prune_level, + use_cond_selectivity, + sort_nest_tables | real_table_bit, + TRUE, limit_applied_to_nest)) + DBUG_RETURN(TRUE); + join->positions[idx].sort_nest_operation_here= FALSE; + trace_rest.end(); + } Json_writer_array trace_rest(thd, "rest_of_plan"); if (best_extension_by_limited_search(join, remaining_tables & ~real_table_bit, @@ -9557,26 +9851,53 @@ best_extension_by_limited_search(JOIN *join, current_read_time, search_depth - 1, prune_level, - use_cond_selectivity)) + use_cond_selectivity, + nest_created ? sort_nest_tables : + sort_nest_tables | real_table_bit, + nest_created, + limit_applied_to_nest)) DBUG_RETURN(TRUE); + trace_rest.end(); + + if (!idx) + limit_applied_to_nest= FALSE; swap_variables(JOIN_TAB*, join->best_ref[idx], *pos); } else - { /* + { + /* 'join' is either the best partial QEP with 'search_depth' relations, or the best complete QEP so far, whichever is smaller. */ - if (join->sort_by_table && - join->sort_by_table != - join->positions[join->const_tables].table->table) - /* - We may have to make a temp table, note that this is only a - heuristic since we cannot know for sure at this point. - Hence it may be wrong. - */ - current_read_time= COST_ADD(current_read_time, current_record_count); + if (!nest_created && + ((join->sort_by_table && + join->sort_by_table != + join->positions[join->const_tables].table->table) || + join->sort_nest_possible)) + { + double cost; + if (join->sort_nest_possible) + { + ulong rec_len= cache_record_length_for_nest(join, idx); + cost= join->sort_nest_oper_cost(partial_join_cardinality, idx, + rec_len); + trace_one_table.add("cost_of_sorting", cost); + } + else + { + /* + We may have to make a temp table, note that this is only a + heuristic since we cannot know for sure at this point. + Hence it may be wrong. + */ + cost= current_record_count; + } + + current_read_time= COST_ADD(current_read_time, cost); + } trace_one_table.add("estimated_join_cardinality", partial_join_cardinality); + if (current_read_time < join->best_read) { memcpy((uchar*) join->best_positions, (uchar*) join->positions, @@ -9590,6 +9911,7 @@ best_extension_by_limited_search(JOIN *join, current_read_time, "full_plan");); } + join->prefix_resolves_ordering= save_prefix_resolves_ordering; restore_prev_nj_state(s); restore_prev_sj_state(remaining_tables, s, idx); } @@ -9598,6 +9920,24 @@ best_extension_by_limited_search(JOIN *join, } +ulong JOIN_TAB::get_estimated_record_length() +{ + if (used_rec_length_for_nest) + return used_rec_length_for_nest; + + Field **f_ptr, *field; + MY_BITMAP *read_set= table->read_set; + ulong rec_length= 0; + for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++) + { + if (bitmap_is_set(read_set, field->field_index)) + rec_length+=field->pack_length(); + } + used_rec_length_for_nest= rec_length; + return used_rec_length_for_nest; +} + + /** Find how much space the prevous read not const tables takes in cache. */ @@ -9817,6 +10157,27 @@ cache_record_length(JOIN *join,uint idx) /* + @brief + Get an estimate of record length for a materialized nest. +*/ + +static ulong +cache_record_length_for_nest(JOIN *join,uint idx) +{ + uint length=0; + JOIN_TAB **pos,**end; + + for (pos=join->best_ref+join->const_tables,end=join->best_ref+idx ; + pos != end ; + pos++) + { + JOIN_TAB *join_tab= *pos; + length+= join_tab->get_estimated_record_length(); + } + return length; +} + +/* Get the number of different row combinations for subset of partial join SYNOPSIS @@ -9868,15 +10229,20 @@ cache_record_length(JOIN *join,uint idx) */ double -prev_record_reads(const POSITION *positions, uint idx, table_map found_ref) +prev_record_reads(const POSITION *positions, uint idx, table_map found_ref, + table_map sort_nest_tables, + double fraction_output_for_nest) { double found=1.0; const POSITION *pos_end= positions - 1; + bool apply_limit= FALSE; for (const POSITION *pos= positions + idx - 1; pos != pos_end; pos--) { if (pos->table->table->map & found_ref) { found_ref|= pos->ref_depend_map; + if (pos->table->table->map & sort_nest_tables) + apply_limit= TRUE; /* For the case of "t1 LEFT JOIN t2 ON ..." where t2 is a const table with no matching row we will get position[t2].records_read==0. @@ -9900,6 +10266,8 @@ prev_record_reads(const POSITION *positions, uint idx, table_map found_ref) } } } + if (apply_limit) + found= COST_MULT(found, fraction_output_for_nest); return found; } @@ -10231,6 +10599,8 @@ bool JOIN::get_best_combination() table_map used_tables; JOIN_TAB *j; KEYUSE *keyuse; + uint n_tables; + table_map nest_tables_map= 0; DBUG_ENTER("get_best_combination"); /* @@ -10262,17 +10632,34 @@ bool JOIN::get_best_combination() full_join=0; hash_join= FALSE; + int index_no= best_positions[const_tables].index_no; + fix_semijoin_strategies_for_picked_join_order(this); top_join_tab_count= get_number_of_tables_at_top_level(this); if (!(join_tab= (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB)* (top_join_tab_count + aggr_tables)))) DBUG_RETURN(TRUE); - + + for (j=join_tab, tablenr=0 ; tablenr < top_join_tab_count + aggr_tables; + tablenr++,j++) + bzero((void*)j, sizeof(JOIN_TAB)); + + if (check_if_sort_nest_present(&n_tables, &nest_tables_map)) + { + if (create_sort_nest_info(n_tables, nest_tables_map)) + DBUG_RETURN(TRUE); + + if (sort_nest_needed()) + join_tab[const_tables + n_tables].is_sort_nest= TRUE; + else + setup_index_use_for_ordering(index_no); + } + JOIN_TAB_RANGE *root_range; if (!(root_range= new (thd->mem_root) JOIN_TAB_RANGE)) DBUG_RETURN(TRUE); - root_range->start= join_tab; + root_range->start= join_tab; /* root_range->end will be set later */ join_tab_ranges.empty(); @@ -10286,6 +10673,23 @@ bool JOIN::get_best_combination() { TABLE *form; POSITION *cur_pos= &best_positions[tablenr]; + + if (j->is_sort_nest) + { + uint tables= n_tables; + j->join= this; + j->table= NULL; + j->ref.key = -1; + j->on_expr_ref= (Item**) &null_ptr; + j->is_sort_nest= TRUE; + j->records_read= calculate_record_count_for_sort_nest(tables); + j->records= (ha_rows) j->records_read; + j->cond_selectivity= 1.0; + sort_nest_info->nest_tab= j; + tablenr--; + continue; + } + if (cur_pos->sj_strategy == SJ_OPT_MATERIALIZE || cur_pos->sj_strategy == SJ_OPT_MATERIALIZE_SCAN) { @@ -10317,6 +10721,10 @@ bool JOIN::get_best_combination() DBUG_RETURN(TRUE); jt_range->start= jt; jt_range->end= jt + sjm->tables; + JOIN_TAB *tab; + for (tab= jt; tab < jt_range->end; tab++) + bzero((void*)tab, sizeof(JOIN_TAB)); + join_tab_ranges.push_back(jt_range, thd->mem_root); j->bush_children= jt_range; sjm_nest_end= jt + sjm->tables; @@ -10347,7 +10755,7 @@ bool JOIN::get_best_combination() j->type=JT_ALL; if (best_positions[tablenr].use_join_buffer && tablenr != const_tables) - full_join= 1; + full_join= 1; } /*if (best_positions[tablenr].sj_strategy == SJ_OPT_LOOSE_SCAN) @@ -10386,6 +10794,8 @@ bool JOIN::get_best_combination() used_tables= OUTER_REF_TABLE_BIT; // Outer row is already read for (j=join_tab, tablenr=0 ; tablenr < table_count ; tablenr++,j++) { + if (j->is_sort_nest) + j++; if (j->bush_children) j= j->bush_children->start; @@ -11090,6 +11500,8 @@ make_outerjoin_info(JOIN *join) tab; tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS)) { + if (tab->is_sort_nest) + continue; TABLE *table= tab->table; TABLE_LIST *tbl= table->pos_in_table_list; TABLE_LIST *embedding= tbl->embedding; @@ -11333,10 +11745,20 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) JOIN_TAB *tab; table_map current_map; i= join->const_tables; + Item *saved_cond= cond; + Sort_nest_info *sort_nest_info= join->sort_nest_info; + if (join->sort_nest_needed()) + cond= sort_nest_info->get_nest_cond(); + for (tab= first_depth_first_tab(join); tab; tab= next_depth_first_tab(join, tab)) { bool is_hj; + if (tab->is_sort_nest) + { + cond= saved_cond; + continue; + } /* first_inner is the X in queries like: @@ -11559,7 +11981,8 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) tab->table->reginfo.impossible_range) DBUG_RETURN(1); } - else if (tab->type == JT_ALL && ! use_quick_range) + else if (tab->type == JT_ALL && ! use_quick_range && + !join->sort_nest_info) { if (!tab->const_keys.is_clear_all() && tab->table->reginfo.impossible_range) @@ -12374,6 +12797,35 @@ end_sj_materialize(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) } +enum_nested_loop_state +end_nest_materialization(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) +{ + int error; + THD *thd= join->thd; + Sort_nest_info *nest_info= join->sort_nest_info; + DBUG_ENTER("end_sj_materialize"); + if (!end_of_records) + { + TABLE *table= nest_info->table; + fill_record(thd, table, table->field, + nest_info->nest_base_table_cols, TRUE, FALSE); + + if (unlikely(thd->is_error())) + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ + if (unlikely((error= table->file->ha_write_tmp_row(table->record[0])))) + { + /* create_myisam_from_heap will generate error if needed */ + if (table->file->is_fatal_error(error, HA_CHECK_DUP) && + create_internal_tmp_table_from_heap(thd, table, + nest_info->tmp_table_param.start_recinfo, + &nest_info->tmp_table_param.recinfo, + error, 1, NULL)) + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ + } + } + DBUG_RETURN(NESTED_LOOP_OK); +} + /* Check whether a join buffer can be used to join the specified table @@ -12541,6 +12993,10 @@ uint check_join_cache_usage(JOIN_TAB *tab, if (cache_level == 0 || !prev_tab) return 0; + + if (!join->is_join_buffering_allowed(tab)) + return 0; + if (force_unlinked_cache && (cache_level%2 == 0)) cache_level--; @@ -12800,7 +13256,9 @@ restart: */ prev_tab= tab - 1; if (tab == join->join_tab + join->const_tables || - (tab->bush_root_tab && tab->bush_root_tab->bush_children->start == tab)) + (tab->bush_root_tab && + tab->bush_root_tab->bush_children->start == tab) || + tab->is_sort_nest) prev_tab= NULL; switch (tab->type) { @@ -12829,7 +13287,7 @@ restart: default: tab->used_join_cache_level= 0; } - if (!tab->bush_children) + if (!tab->bush_children && !tab->is_sort_nest) idx++; } } @@ -12972,17 +13430,20 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) Later it should be improved. */ - if (tab->bush_root_tab && tab->bush_root_tab->bush_children->start == tab) + if ((tab->bush_root_tab && tab->bush_root_tab->bush_children->start == tab) || + tab->is_sort_nest) prev_tab= NULL; - DBUG_ASSERT(tab->bush_children || tab->table == join->best_positions[i].table->table); + DBUG_ASSERT(tab->bush_children || tab->table == join->best_positions[i].table->table + || tab->is_sort_nest); tab->partial_join_cardinality= join->best_positions[i].records_read * (prev_tab? prev_tab->partial_join_cardinality : 1); - if (!tab->bush_children) + if (!tab->bush_children && !tab->is_sort_nest) i++; } check_join_cache_usage_for_tables(join, options, no_jbuf_after); + Sort_nest_info *sort_nest_info= join->sort_nest_info; JOIN_TAB *first_tab; for (tab= first_tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); @@ -13009,7 +13470,8 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) end_sj_materialize. */ if (!(tab->bush_root_tab && - tab->bush_root_tab->bush_children->end == tab + 1)) + tab->bush_root_tab->bush_children->end == tab + 1) && + !(sort_nest_info && tab+1 == sort_nest_info->nest_tab)) { tab->next_select=sub_select; /* normal select */ } @@ -13124,8 +13586,11 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) tab->select->quick->index != MAX_KEY && //not index_merge table->covering_keys.is_set(tab->select->quick->index)) table->file->ha_start_keyread(tab->select->quick->index); - else if (!table->covering_keys.is_clear_all() && - !(tab->select && tab->select->quick)) + else if ((!table->covering_keys.is_clear_all() && + !(tab->select && tab->select->quick)) || + (sort_nest_info && sort_nest_info->nest_tab == tab && + sort_nest_info->number_of_tables() == 1 && + sort_nest_info->index_used >= 0)) { // Only read index tree if (tab->loosescan_match_tab) tab->index= tab->loosescan_key; @@ -13144,9 +13609,17 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) tab->index= table->s->primary_key; else #endif - tab->index=find_shortest_key(table, & table->covering_keys); + if (sort_nest_info && sort_nest_info->number_of_tables() == 1 && + sort_nest_info->nest_tab == tab && + sort_nest_info->index_used >= 0) + { + tab->index= sort_nest_info->index_used; + tab->limit= tab->records_read; + } + else + tab->index=find_shortest_key(table, &table->covering_keys); } - tab->read_first_record= join_read_first; + tab->read_first_record= join_read_first; /* Read with index_first / index_next */ tab->type= tab->type == JT_ALL ? JT_NEXT : JT_HASH_NEXT; } @@ -13199,7 +13672,7 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after) It could be that sort_by_tab==NULL, and the plan is to use filesort() on the first table. */ - if (join->order) + if (join->order && !join->sort_nest_info) { join->simple_order= 0; join->need_tmp= 1; @@ -13957,6 +14430,8 @@ static void update_depend_map(JOIN *join) join_tab; join_tab= next_linear_tab(join, join_tab, WITH_BUSH_ROOTS)) { + if (join_tab->is_sort_nest) + continue; TABLE_REF *ref= &join_tab->ref; table_map depend_map=0; Item **item=ref->items; @@ -14247,6 +14722,22 @@ remove_const(JOIN *join,ORDER *first_order, COND *cond, } +bool JOIN::remove_const_from_order_by() +{ + ORDER *org_order= order; + order=remove_const(this, order,conds,1, &simple_order); + if (unlikely(thd->is_error())) + return TRUE; + /* + If we are using ORDER BY NULL or ORDER BY const_expression, + return result in any order (even if we are using a GROUP BY) + */ + if (!order && org_order) + skip_sort_order= 1; + return FALSE; +} + + /** Filter out ORDER items those are equal to constants in WHERE @@ -15731,11 +16222,11 @@ Item *eliminate_item_equal(THD *thd, COND *cond, COND_EQUAL *upper_levels, The transformed condition, or NULL in case of error */ -static COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, - COND *cond, - COND_EQUAL *cond_equal, - void *table_join_idx, - bool do_substitution) +COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, + COND *cond, + COND_EQUAL *cond_equal, + void *table_join_idx, + bool do_substitution) { Item_equal *item_equal; COND *org_cond= cond; // Return this in case of fatal error @@ -15856,7 +16347,7 @@ static COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, { REPLACE_EQUAL_FIELD_ARG arg= {item_equal, context_tab}; if (!(cond= cond->transform(thd, &Item::replace_equal_field, - (uchar *) &arg))) + FALSE, (uchar *) &arg))) return 0; } cond_equal= cond_equal->upper_levels; @@ -16853,7 +17344,8 @@ void optimize_wo_join_buffering(JOIN *join, uint first_tab, uint last_tab, best_access_path(join, rs, reopt_remaining_tables, join->positions, i, TRUE, rec_count, - &pos, &loose_scan_pos); + &pos, &loose_scan_pos, + 0, FALSE); } else pos= join->positions[i]; @@ -17690,6 +18182,16 @@ const_expression_in_where(COND *cond, Item *comp_item, Field *comp_field, } } } + else + { + if (!comp_item) + return 0; + Item_equal *item_eq= comp_item->get_item_equal(); + if (!item_eq || !item_eq->get_const()) + return 0; + *const_item= item_eq->get_const(); + return 1; + } return 0; } @@ -19986,6 +20488,10 @@ do_select(JOIN *join, Procedure *procedure) JOIN_TAB *join_tab= join->join_tab + (join->tables_list ? join->const_tables : 0); + Sort_nest_info *sort_nest_info= join->sort_nest_info; + join_tab= sort_nest_info ? sort_nest_info->nest_tab + : join_tab; + if (join->outer_ref_cond && !join->outer_ref_cond->val_int()) error= NESTED_LOOP_NO_MORE_ROWS; else @@ -22668,9 +23174,9 @@ part_of_refkey(TABLE *table,Field *field) -1 Reverse key can be used */ -static int test_if_order_by_key(JOIN *join, - ORDER *order, TABLE *table, uint idx, - uint *used_key_parts) +int test_if_order_by_key(JOIN *join, + ORDER *order, TABLE *table, uint idx, + uint *used_key_parts) { KEY_PART_INFO *key_part,*key_part_end; key_part=table->key_info[idx].key_part; @@ -22692,6 +23198,8 @@ static int test_if_order_by_key(JOIN *join, for (; order ; order=order->next, const_key_parts>>=1) { + if (order->item[0]->real_item()->type() != Item::FIELD_ITEM) + DBUG_RETURN(0); Item_field *item_field= ((Item_field*) (*order->item)->real_item()); Field *field= item_field->field; int flag; @@ -26274,6 +26782,13 @@ bool JOIN_TAB::save_explain_data(Explain_table_access *eta, ctab->emb_sj_nest->sj_subq_pred->get_identifier()); eta->table_name.copy(table_name_buffer, len, cs); } + else if (is_sort_nest) + { + size_t len= my_snprintf(table_name_buffer, + sizeof(table_name_buffer)-1, + "<sort-nest>"); + eta->table_name.copy(table_name_buffer, len, cs); + } else { TABLE_LIST *real_table= table->pos_in_table_list; @@ -26646,6 +27161,14 @@ bool JOIN_TAB::save_explain_data(Explain_table_access *eta, prev_table->derived_select_number); eta->firstmatch_table_name.append(namebuf, len); } + else if(do_firstmatch->is_sort_nest) + { + char namebuf[NAME_LEN]; + /* Derived table name generation */ + size_t len= my_snprintf(namebuf, sizeof(namebuf)-1, + "<sort-nest>"); + eta->firstmatch_table_name.append(namebuf, len); + } else eta->firstmatch_table_name.append(&prev_table->pos_in_table_list->alias); } @@ -27883,12 +28406,9 @@ void JOIN::cache_const_exprs() to use a full index scan on this index). */ -static bool get_range_limit_read_cost(const JOIN_TAB *tab, - const TABLE *table, - ha_rows table_records, - uint keynr, - ha_rows rows_limit, - double *read_time) +bool get_range_limit_read_cost(const JOIN_TAB *tab, const TABLE *table, + ha_rows table_records, uint keynr, + ha_rows rows_limit, double *read_time) { bool res= false; /* @@ -28242,78 +28762,11 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, select_limit= (ha_rows) (select_limit*rec_per_key); } /* group */ - /* - If tab=tk is not the last joined table tn then to get first - L records from the result set we can expect to retrieve - only L/fanout(tk,tn) where fanout(tk,tn) says how many - rows in the record set on average will match each row tk. - Usually our estimates for fanouts are too pessimistic. - So the estimate for L/fanout(tk,tn) will be too optimistic - and as result we'll choose an index scan when using ref/range - access + filesort will be cheaper. - */ - select_limit= (ha_rows) (select_limit < fanout ? - 1 : select_limit/fanout); - - /* - refkey_rows_estimate is E(#rows) produced by the table access - strategy that was picked without regard to ORDER BY ... LIMIT. - - It will be used as the source of selectivity data. - Use table->cond_selectivity as a better estimate which includes - condition selectivity too. - */ - { - // we use MIN(...), because "Using LooseScan" queries have - // cond_selectivity=1 while refkey_rows_estimate has a better - // estimate. - refkey_rows_estimate= MY_MIN(refkey_rows_estimate, - ha_rows(table_records * - table->cond_selectivity)); - } + find_cost_of_index_with_ordering(thd, tab, table, &select_limit, + fanout, refkey_rows_estimate, + nr, &index_scan_time, + &possible_key); - /* - We assume that each of the tested indexes is not correlated - with ref_key. Thus, to select first N records we have to scan - N/selectivity(ref_key) index entries. - selectivity(ref_key) = #scanned_records/#table_records = - refkey_rows_estimate/table_records. - In any case we can't select more than #table_records. - N/(refkey_rows_estimate/table_records) > table_records - <=> N > refkey_rows_estimate. - */ - - if (select_limit > refkey_rows_estimate) - select_limit= table_records; - else - select_limit= (ha_rows) (select_limit * - (double) table_records / - refkey_rows_estimate); - possible_key.add("updated_limit", select_limit); - rec_per_key= keyinfo->actual_rec_per_key(keyinfo->user_defined_key_parts-1); - set_if_bigger(rec_per_key, 1); - /* - Here we take into account the fact that rows are - accessed in sequences rec_per_key records in each. - Rows in such a sequence are supposed to be ordered - by rowid/primary key. When reading the data - in a sequence we'll touch not more pages than the - table file contains. - TODO. Use the formula for a disk sweep sequential access - to calculate the cost of accessing data rows for one - index entry. - */ - index_scan_time= select_limit/rec_per_key * - MY_MIN(rec_per_key, table->file->scan_time()); - double range_scan_time; - if (get_range_limit_read_cost(tab, table, table_records, nr, - select_limit, &range_scan_time)) - { - possible_key.add("range_scan_time", range_scan_time); - if (range_scan_time < index_scan_time) - index_scan_time= range_scan_time; - } - possible_key.add("index_scan_time", index_scan_time); if ((ref_key < 0 && (group || table->force_index || is_covering)) || index_scan_time < read_time) @@ -28869,6 +29322,151 @@ select_handler *SELECT_LEX::find_select_handler(THD *thd) } +/* + @brief + Check if the items in the ORDER BY clause are expensive or not. + + @details + In this function we walk through the ORDER BY list and check if + the elements in the list are expensive to calculate or not. + This is done so that we can force computing the join first, store + the result in a temporary table and then sort the temporary table. + + @retval + TRUE ORDER BY clause is expensive + FALSE Otherwise +*/ + +bool JOIN::is_order_by_expensive() +{ + /* + Force using of temp table if sorting by a SP or UDF function due to + their expensive and probably non-deterministic nature. + */ + for (ORDER *tmp_order= order; tmp_order ; tmp_order=tmp_order->next) + { + Item *item= *tmp_order->item; + if (item->is_expensive()) + return TRUE; + } + return FALSE; +} + + + +/* + @brief + Estimate the cardinality for a join + + @param + joined_tables map of all the non-const tables of the join + + @details + Run the join planner to get an estimate of cardinality for a join. + + @note + This is currently used by the ORDER BY LIMIT optimization with the + sort-nest. This is a very expensive way to compute cardinality. + + The cardinality of the join remains the same irrespective of the way the + tables are joined theoretically but that is not the case with our join + planner. + + Still we would like to speed this process of getting the estimates of + cardinality for a join by reducing the number of permutations of + join orders to be considered. We can do this by running the join planner + with a small value for system variable optimizer_search_depth. + + @retval + TRUE error + FALSE otherwise +*/ + +bool JOIN::estimate_cardinality_for_join(table_map joined_tables) +{ + Json_writer_temp_disable disable_tracing(thd); + uint use_cond_selectivity; + uint search_depth= thd->variables.optimizer_search_depth; + if (search_depth == 0) + search_depth= determine_search_depth(this); + uint prune_level= thd->variables.optimizer_prune_level; + use_cond_selectivity= thd->variables.optimizer_use_condition_selectivity; + + TABLE *save_sort_by_table= sort_by_table; + JOIN_TAB *save_best_ref[MAX_TABLES]; + for (uint i= 0; i < table_count; i++) + save_best_ref[i]= best_ref[i]; + + get_cardinality_estimate= TRUE; + cardinality_estimate= DBL_MAX; + + if (greedy_search(this, joined_tables, search_depth, prune_level, + use_cond_selectivity)) + return TRUE; + + set_if_bigger(join_record_count, 1); + cardinality_estimate= join_record_count; + + cur_embedding_map= 0; + reset_nj_counters(this, join_list); + cur_sj_inner_tables= 0; + get_cardinality_estimate= FALSE; + sort_by_table= save_sort_by_table; + + for (uint i= 0; i < table_count; i++) + best_ref[i]= save_best_ref[i]; + + return FALSE; +} + + +/* + @brief + Re-setup accesses that use index on the first non-const table for ordering + + @param + tab table whose access needs to be re-setup + idx index used to access first non-const table + + @details + Re-setup the way to access index when the ordering is in DESCENDING order. + Also cancel the Index Condition Pushdown for the indexes that need to + do the ordering in reverse order. +*/ + +void resetup_access_for_ordering(JOIN_TAB* tab, int idx) +{ + JOIN *join= tab->join; + int direction= test_if_order_by_key(join, join->order, tab->table, idx); + if (direction == -1) + { + if (tab->type == JT_REF || tab->type == JT_EQ_REF) + { + tab->read_first_record= join_read_last_key; + tab->read_record.read_record_func= join_read_prev_same; + /* + Cancel Pushed Index Condition, as it doesn't work for reverse scans. + */ + if (tab->select && tab->select->pre_idx_push_select_cond) + { + tab->set_cond(tab->select->pre_idx_push_select_cond); + tab->table->file->cancel_pushed_idx_cond(); + } + } + else if (tab->type == JT_NEXT) + tab->read_first_record= join_read_last; + else if (tab->type == JT_ALL && tab->select && tab->select->quick) + { + if (tab->select && tab->select->pre_idx_push_select_cond) + { + tab->set_cond(tab->select->pre_idx_push_select_cond); + tab->table->file->cancel_pushed_idx_cond(); + } + } + } +} + + /** @brief Construct not null conditions for provingly not nullable fields diff --git a/sql/sql_select.h b/sql/sql_select.h index e3501fb98c6..0685040612c 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -366,6 +366,12 @@ typedef struct st_join_table { uint used_fields; ulong used_fieldlength; ulong max_used_fieldlength; + /* + Estimate for the length of a record that would be stored in the nest. + The estimate contains length of all the fields that have bitmap read_set + set for them. + */ + ulong used_rec_length_for_nest; uint used_blobs; uint used_null_fields; uint used_uneven_bit_fields; @@ -524,6 +530,12 @@ typedef struct st_join_table { /* Becomes true just after the used range filter has been built / filled */ bool is_rowid_filter_built; + /* + Set to TRUE if we picked a join order that would use a sort-nest on + a prefix that satisfies the ORDER BY clause. + */ + bool is_sort_nest; + void build_range_rowid_filter_if_needed(); void cleanup(); @@ -611,6 +623,7 @@ typedef struct st_join_table { return tmp_select_cond; } void calc_used_field_length(bool max_fl); + ulong get_estimated_record_length(); ulong get_used_fieldlength() { if (!used_fieldlength) @@ -676,6 +689,10 @@ typedef struct st_join_table { table_map remaining_tables); bool fix_splitting(SplM_plan_info *spl_plan, table_map remaining_tables, bool is_const_table); + int get_index_on_table(); + void find_keys_that_can_achieve_ordering(); + bool check_if_index_satisfies_ordering(int index_used); + bool needs_filesort(uint idx, int index_used); } JOIN_TAB; @@ -859,7 +876,9 @@ public: bool disable_jbuf, double record_count, struct st_position *pos, - struct st_position *loose_scan_pos); + struct st_position *loose_scan_pos, + table_map sort_nest_tables, + bool nest_created); friend bool get_best_combination(JOIN *join); friend int setup_semijoin_loosescan(JOIN *join); friend void fix_semijoin_strategies_for_picked_join_order(JOIN *join); @@ -992,6 +1011,20 @@ typedef struct st_position /* Cost info for the range filter used at this position */ Range_rowid_filter_cost_info *range_rowid_filter_info; + /* + Flag to be set to TRUE if prefix of a join satisfies the ORDER BY CLAUSE + This flag is only set at the join planner stage. + */ + bool sort_nest_operation_here; + /* + set to the index number for the first non-const table only if it + satisfies the ORDER BY CLAUSE and has the lowest cost of accessing + the table. + -1 otherwise + This is set only at the join planner stage. + */ + int index_no; + } POSITION; typedef Bounds_checked_array<Item_null_result*> Item_null_array; @@ -1065,6 +1098,96 @@ private: }; +/** + Structure which consists of an item of the base table and the corresponding + item to the base item in the nest. +*/ + +class Item_pair :public Sql_alloc +{ +public: + Item *base_item; + Item *nest_item; + Item_pair(Item *a, Item *b) + :base_item(a), nest_item(b) {} +}; + + +/* + A subset of joined tables that are joined together is called a join table + nest. If the result of the join these tables is materialized in a temporary + table then the nest is called a materialized join table nest. + The class declared below is used for the objects created to handle + materialized join tables nests. These objects are supposed to be used at the + optimization an execution phases. +*/ + +class Mat_join_tab_nest_info : public Sql_alloc +{ +protected: + /* The join which the joined tables from the nest belongs to */ + JOIN *join; + /* Number of joined tables in the nest */ + uint n_tables; + /* The bitmap of joined tables from the nest */ + table_map nest_tables_map; + /* Condition extracted from the WHERE condition and pushed into the nest */ + Item *nest_cond; + /* TRUE <=> materialization already performed */ + bool materialized; + +public: + Mat_join_tab_nest_info(JOIN *join_arg, uint tables, table_map tables_map) + { + join= join_arg; + table= NULL; + nest_tab= NULL; + n_tables= tables; + nest_tables_map= tables_map; + nest_cond= NULL; + materialized= FALSE; + } + + TMP_TABLE_PARAM tmp_table_param; + List<Item> nest_base_table_cols; + + /* + List containing the mapping of items of the base tables with the + corresponding items in the nest + */ + List<Item_pair> mapping_of_items; + st_join_table *nest_tab; + TABLE *table; + + uint number_of_tables() { return n_tables; } + table_map get_tables_map() { return nest_tables_map; } + void set_nest_cond(Item *cond) { nest_cond= cond; } + Item *get_nest_cond() { return nest_cond; } + void set_materialized() { materialized= TRUE; } + bool is_materialized() { return materialized; } +}; + +/* + A derived class for the sort-nest. +*/ + +class Sort_nest_info : public Mat_join_tab_nest_info +{ +public: + Sort_nest_info(JOIN *join_arg, uint tables, table_map tables_map) + :Mat_join_tab_nest_info(join_arg, tables, tables_map) + { + index_used= -1; + } + /* + >=0 set to the index that satisfies the ORDER BY clause and does an index + scan on the first non-const table. + -1 otherwise + */ + int index_used; +}; + + class JOIN :public Sql_alloc { private: @@ -1509,6 +1632,45 @@ public: */ bool is_orig_degenerated; + /* + NOT NULL sort nest present + NULL Otherwise + */ + Sort_nest_info *sort_nest_info; + + /* + Set to TRUE if the query can use ORDER BY LIMIT optimization with + sort-nest. It caches the value that tells if the join optimizer + should consider using a sort-nest or not. + @see sort_nest_allowed() + */ + bool sort_nest_possible; + + /* + SET to TRUE when one wants to get an estimate of the cardinality for a + join. + */ + bool get_cardinality_estimate; + + /* + Set to the estimate of the rows that the join planner expects to be in the + output of the join. + */ + double cardinality_estimate; + + /* + The fraction of records we would read of the sort-nest or the first table + that satisfies the ORDER BY clause, after the sorting is done. + */ + double fraction_output_for_nest; + + /* + Caches that a prefix resolved the ORDER BY clause. This is done so that + we don't walk through the order by list everytime when we extend the prefix + to check if the extended prefix satifies the ordering or not. + */ + bool prefix_resolves_ordering; + JOIN(THD *thd_arg, List<Item> &fields_arg, ulonglong select_options_arg, select_result *result_arg) :fields_list(fields_arg) @@ -1604,6 +1766,12 @@ public: sjm_lookup_tables= 0; sjm_scan_tables= 0; is_orig_degenerated= false; + sort_nest_info= NULL; + sort_nest_possible= FALSE; + fraction_output_for_nest= 1; + prefix_resolves_ordering= FALSE; + get_cardinality_estimate= FALSE; + cardinality_estimate= DBL_MAX; } /* True if the plan guarantees that it will be returned zero or one row */ @@ -1722,7 +1890,8 @@ public: JOIN_TAB *get_sort_by_join_tab() { return (need_tmp || !sort_by_table || skip_sort_order || - ((group || tmp_table_param.sum_func_count) && !group_list)) ? + ((group || tmp_table_param.sum_func_count) && !group_list) || + sort_nest_info) ? NULL : join_tab+const_tables; } bool setup_subquery_caches(); @@ -1741,6 +1910,7 @@ public: and one of the following conditions holds: - We are using DISTINCT (simple distinct's are already optimized away) - We are using an ORDER BY or GROUP BY on fields not in the first table + - We are not using the sort nest for ORDER BY with LIMIT - We are using different ORDER BY and GROUP BY orders - The user wants us to buffer the result. When the WITH ROLLUP modifier is present, we cannot skip temporary table @@ -1749,11 +1919,49 @@ public: bool test_if_need_tmp_table() { return ((const_tables != table_count && - ((select_distinct || !simple_order || !simple_group) || + ((select_distinct || (!simple_order && !sort_nest_info) || !simple_group) || (group_list && order) || MY_TEST(select_options & OPTION_BUFFER_RESULT))) || (rollup.state != ROLLUP::STATE_NONE && select_distinct)); } + + /* + TRUE if the sort-nest contains more than one table + FALSE otherwise + */ + bool sort_nest_needed() + { + if (!sort_nest_info) + return FALSE; + return sort_nest_info->number_of_tables() == 1 ? FALSE : TRUE; + } + + bool sort_nest_allowed(); + bool is_order_by_expensive(); + bool estimate_cardinality_for_join(table_map joined_tables); + bool check_if_sort_nest_present(uint* n_tables, table_map *tables_map); + bool create_sort_nest_info(uint n_tables, table_map nest_tables_map); + bool remove_const_from_order_by(); + bool make_sort_nest(Mat_join_tab_nest_info *nest_info); + double calculate_record_count_for_sort_nest(uint n_tables); + void + substitute_base_with_nest_field_items(Mat_join_tab_nest_info* nest_info); + void substitute_best_fields_for_order_by_items(); + void substitute_ref_items(JOIN_TAB *tab, Mat_join_tab_nest_info* nest_info); + void substitutions_for_sjm_lookup(JOIN_TAB *sjm_tab, + Mat_join_tab_nest_info* nest_info); + void extract_condition_for_the_nest(Mat_join_tab_nest_info* nest_info); + void propagate_equal_field_for_orderby(); + void setup_index_use_for_ordering(int index_no); + void setup_range_scan(JOIN_TAB *tab, uint idx, double records); + bool is_join_buffering_allowed(JOIN_TAB *tab); + bool check_join_prefix_resolves_ordering(table_map previous_tables); + bool consider_adding_sort_nest(table_map previous_tables); + void set_fraction_output_for_nest(); + double sort_nest_oper_cost(double join_record_count, uint idx, + ulong rec_len); + bool is_index_with_ordering_allowed(uint idx); + bool choose_subquery_plan(table_map join_tables); void get_partial_cost_and_fanout(int end_tab_idx, table_map filter_map, @@ -2104,6 +2312,14 @@ bool mysql_select(THD *thd, TABLE_LIST *tables, List<Item> &list, void free_underlaid_joins(THD *thd, SELECT_LEX *select); bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result); +double calculate_record_count_for_sort_nest(JOIN *join, uint n_tables); +void check_cond_extraction_for_nest(THD *thd, Item *cond, + Pushdown_checker checker, uchar* arg); +void resetup_access_for_ordering(JOIN_TAB* tab, int idx); +int get_best_index_for_order_by_limit(JOIN_TAB *tab, + ha_rows select_limit_arg, + double *read_time, double *records, + int index_used, uint idx); /* General routine to change field->ptr of a NULL-terminated array of Field @@ -2448,10 +2664,13 @@ bool instantiate_tmp_table(TABLE *table, KEY *keyinfo, ulonglong options); bool open_tmp_table(TABLE *table); void setup_tmp_table_column_bitmaps(TABLE *table, uchar *bitmaps); -double prev_record_reads(const POSITION *positions, uint idx, table_map found_ref); +double prev_record_reads(const POSITION *positions, uint idx, table_map found_ref, + table_map sort_nest_tables, + double fraction_output_for_nest); + void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist); -double get_tmp_table_lookup_cost(THD *thd, double row_count, uint row_size); -double get_tmp_table_write_cost(THD *thd, double row_count, uint row_size); +double get_tmp_table_lookup_cost(THD *thd, double row_count, ulong row_size); +double get_tmp_table_write_cost(THD *thd, double row_count, ulong row_size); void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array); bool sort_and_filter_keyuse(THD *thd, DYNAMIC_ARRAY *keyuse, bool skip_unprefixed_keyparts); diff --git a/sql/sql_sort_nest.cc b/sql/sql_sort_nest.cc new file mode 100644 index 00000000000..87773414342 --- /dev/null +++ b/sql/sql_sort_nest.cc @@ -0,0 +1,1596 @@ +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2008, 2017, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + + +#include "mariadb.h" +#include "sql_select.h" +#include "opt_trace.h" + +/** + +INTRODUCTION + +This file contains the functions to support the cost based ORDER BY with LIMIT +optimization. + +The motivation behind this optimization is to shortcut the join execution +for queries having ORDER BY with LIMIT clause. In other words we would like to +avoid computing the entire join for queries having ORDER BY with LIMIT. + +The main idea behind this optimization is to push the LIMIT to a partial join. +For pushing the LIMIT there is one pre-requisite and that is the partial +join MUST resolve the ORDER BY clause. + +What does PUSHING THE LIMIT mean? + +Pushing the limit to a partial join means that one would only read a fraction +of records of the prefix that are sorted in accordance with the ORDER BY +clause. + + +Let's say we have tables + t1, t2, t3, t4 .............tk,tk+1.........................tn + |<---------prefix------------>|<-------suffix---------------> + +and lets assume the prefix can resolve the ORDER BY clause and we can push +the LIMIT. + +Mathematically speaking the fanout of the suffix in the join wrt prefix would +help us to estimate the fraction of records of the prefix(that are sorted) +that would be read: + + +-------------------------------------------------------+ + | | +fanout(tk+1....tn)= | cardinality(t1,t2....tn) / cardinality(t1,t2....tk) | + | | + +-------------------------------------------------------+ + +fanout is always >= 1 + +So number of records that one would read for the prefix after the LIMIT is +pushed is + + +----------------------------+ + | | +records_read= | LIMIT * fanout(tk+1....tn) | + | | + +----------------------------+ + + +--------------------------------------------------------------+ + | | + = | LIMIT * (cardinality(t1,t2....tk) / cardinality(t1,t2....tn))| + | | + +--------------------------------------------------------------+ + + +So the LIMIT is pushed for all partial join orders enumerated by the join +planner that can resolve the ORDER BY clause. +This is how we achieve a complete cost based solution for +ORDER BY with LIMIT optimization. + + +IMPLEMENTATION DETAILS + +Let us divide the implementation details in 3 stages: + +OPTIMIZATION STAGE + +- We invoke the join planner to get an estimate of the cardinality of the + join. This is needed for pushing the LIMIT in different partial plans + which can resolve the ORDER BY clause. + +- Join planner is invoked again to find the best join order for the tables + inside the join. The join planner enumerated various join orders. + For each partial plan we try to find out if it can resolve the ORDER BY + clause or not. + To resolve the ORDER BY clause, equalities from the WHERE clause are also + considered. + +- After a partial plan that can resolve ORDER BY clause is found, we push + the LIMIT to the partial plan. + +- Access methods that ensure pre-existing ordering are also taken into account + inside the join planner. There can be indexes on the first non-const table + that can resolve the ORDER BY clause. So we push the LIMIT to the first + non-const table also. + +- For each partial plan that can resolve the ORDER BY clause, + we consider 2 cases + 1) Push the LIMIT at the current partial plan + 2) Push the LIMIT later + + This helps us to enumerate all plans where on can push LIMIT at different + partial plans. Finally the plan with the lowest cost is picked by the join + planner + + +COMPILATION STAGE + +Preparation of Sort Nest + +Let's say we have the best join order as: + + t1, t2, t3, t4 .............tk,tk+1.........................tn + |<---------prefix------------>|<-------suffix---------------> + + +The array of join_tab structures would look like + + t1, t2, t3, t4 .............tk, <sort nest>, tk+1.........................tn + +After the best execution plan is picked by the join planner which requires +a nest for a prefix of tables that can resolve the ORDER BY clause, we want +to prepare the temporary table that would hold the result of materialization +of the tables in the prefix. + +t1, t2, t3, t4..............tk ======> inner tables of the nest + +To create the temporary table we need a list of Items which we want to store +inside the temporary table of the nest. Currently this list contains all +fields of the inner tables of the nest that have their bitmap read_set set. +With this list of Items we create the temporary table for the nest. +Also we create a list of Items for all the fields of the temporary table. +This list is needed for substitution of items that will be evaluated in the +POST ORDER BY context. + + +After the nest for the prefix is prepared, we extract a sub-condition which is +dependent on the inner tables of the nest from the WHERE clause. This +condition is then attached to the inner tables of the nest. This condition +would be evaluated before the ORDER BY clause is applied to the temporary +table of the nest. + +We need to make substitution for items belonging to the inner tables of the +nest which will be evaluated in the POST ORDER BY context. These items need +to be substituted with the corresponding items of the temporary table +of the nest. + + +EXECUTION STAGE + +Let's say we have the best join order as: + + t1, t2, t3, t4 .............tk,tk+1.........................tn + |<---------prefix------------>|<-------suffix---------------> + + The prefix are the inner table of the sort nest while the suffix are the + tables outside the sort nest. + + As soon as the join execution starts, we compute the partial join for the + tables in the prefix and store the result inside the temporary table + for the sort nest. + Then we sort the temporary table in accordance with the ORDER BY clause. + After the sort is performed we read the records from the temporary + table of the sort nest one by one and continue the join with the + tables in the suffix. + + The join execution for this optimization can be split in 3 parts + + a) Materialize the prefix + materialize + t1, t2, t3, t4 .............tk ============> <sort nest> + |<---------prefix------------>| + + b) Sort the <sort nest> in accordance with the ORDER BY clause + + c) Read records from the Filesort buffer one by one and continue join + execution with the tables in the suffix + + <sort nest>, tk+1.........................tn + <-------suffix----------------> + + The execution stops as soon as we get LIMIT records in the output. + +*/ + +int test_if_order_by_key(JOIN *join, ORDER *order, TABLE *table, uint idx, + uint *used_key_parts= NULL); +COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab, + COND *cond, + COND_EQUAL *cond_equal, + void *table_join_idx, + bool do_substitution); +enum_nested_loop_state +end_nest_materialization(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); +bool get_range_limit_read_cost(const JOIN_TAB *tab, const TABLE *table, + ha_rows table_records, uint keynr, + ha_rows rows_limit, double *read_time); +Item **get_sargable_cond(JOIN *join, TABLE *table); +void find_cost_of_index_with_ordering(THD *thd, const JOIN_TAB *tab, + TABLE *table, + ha_rows *select_limit_arg, + double fanout, double est_best_records, + uint nr, double *index_scan_time, + Json_writer_object *trace_possible_key); + + +/* + @brief + Substitute field items of tables inside the nest with nest's field items. + + @param nest_info Structure holding information about the nest + + @details + Substitute field items of tables inside the sort-nest with sort-nest's + field items. This is needed for expressions which would be + evaluated in the post ORDER BY context. + + Example: + + SELECT * FROM t1, t2, t3 + WHERE t1.a = t2.a AND t2.b = t3.b AND t1.c > t3.c + ORDER BY t1.a,t2.c + LIMIT 5; + + Let's say in this case the join order is t1,t2,t3 and there is a sort-nest + on the prefix t1,t2. + + Now looking at the WHERE clause, splitting it into 2 parts: + (1) t2.b = t3.b AND t1.c > t3.c ---> condition external to the nest + (2) t1.a = t2.a ---> condition internal to the nest + + Now look at the condition in (1), this would be evaluated in the post + ORDER BY context. + + So t2.b and t1.c should actually refer to the sort-nest's field items + instead of field items of the tables inside the sort-nest. + This is why we need to substitute field items of the tables inside the + sort-nest with sort-nest's field items. + + For the condition in (2) there is no need for substitution as this + condition is internal to the nest and would be evaluated before we + do the sorting for the sort-nest. + + This function does the substitution for + - WHERE clause + - SELECT LIST + - ORDER BY clause + - ON expression + - REF access items +*/ + +void +JOIN::substitute_base_with_nest_field_items(Mat_join_tab_nest_info *nest_info) +{ + List_iterator<Item> it(fields_list); + Item *item, *new_item; + + /* Substituting SELECT list field items with sort-nest's field items */ + while ((item= it++)) + { + if ((new_item= item->transform(thd, + &Item::replace_with_nest_items, TRUE, + (uchar *) nest_info)) != item) + { + new_item->name= item->name; + thd->change_item_tree(it.ref(), new_item); + } + new_item->update_used_tables(); + } + + /* Substituting ORDER BY field items with sort-nest's field items */ + ORDER *ord; + for (ord= order; ord ; ord=ord->next) + { + (*ord->item)= (*ord->item)->transform(thd, + &Item::replace_with_nest_items, + TRUE, (uchar *) nest_info); + (*ord->item)->update_used_tables(); + } + + JOIN_TAB *end_tab= nest_info->nest_tab; + uint i, j; + for (i= const_tables + nest_info->number_of_tables(), j=0; + i < top_join_tab_count; i++, j++) + { + JOIN_TAB *tab= end_tab + j; + + if (tab->type == JT_REF || tab->type == JT_EQ_REF || + tab->type == JT_REF_OR_NULL) + substitute_ref_items(tab, nest_info); + + /* Substituting ON-EXPR field items with sort-nest's field items */ + if (*tab->on_expr_ref) + { + item= (*tab->on_expr_ref)->transform(thd, + &Item::replace_with_nest_items, + TRUE, (uchar *) nest_info); + *tab->on_expr_ref= item; + (*tab->on_expr_ref)->update_used_tables(); + } + + /* + Substituting REF field items for SJM lookup with sort-nest's field items + */ + if (tab->bush_children) + substitutions_for_sjm_lookup(tab, nest_info); + } + + /* + This needs a pointer to the nest, so that this could be used in general + */ + extract_condition_for_the_nest(nest_info); + + /* Substituting WHERE clause's field items with sort-nest's field items */ + if (conds) + { + conds= conds->transform(thd, &Item::replace_with_nest_items, TRUE, + (uchar *) nest_info); + conds->update_used_tables(); + } +} + + +/* + @brief + Substitute ref access field items with nest's field items. + + @param tab join tab structure having ref access + @param nest_info Structure holding information about the nest + +*/ + +void JOIN::substitute_ref_items(JOIN_TAB *tab, + Mat_join_tab_nest_info* nest_info) +{ + Item *item; + /* Substituting REF field items with sort-nest's field items */ + for (uint keypart= 0; keypart < tab->ref.key_parts; keypart++) + { + item= tab->ref.items[keypart]->transform(thd, + &Item::replace_with_nest_items, + TRUE, (uchar *) nest_info); + if (item != tab->ref.items[keypart]) + { + tab->ref.items[keypart]= item; + Item *real_item= item->real_item(); + store_key *key_copy= tab->ref.key_copy[keypart]; + if (key_copy->type() == store_key::FIELD_STORE_KEY) + { + store_key_field *field_copy= ((store_key_field *)key_copy); + DBUG_ASSERT(real_item->type() == Item::FIELD_ITEM); + field_copy->change_source_field((Item_field *) real_item); + } + } + } +} + + +/* + @brief + Substitute the left expression of the IN subquery with nest's field items. + + @param sjm_tab SJM lookup join tab + @param nest_info Structure holding information about the nest + + @details + This substitution is needed for SJM lookup when the SJM materialized + table is outside the nest. + + For example: + SELECT t1.a, t2.a + FROM t1, t2 + WHERE ot1.a in (SELECT it.b FROM it) AND ot1.b = t1.b + ORDER BY t1.a desc, ot1.a desc + LIMIT 5; + + Lets consider the join order here is t1, t2, <subquery2> and there is a + nest on t1, t2. For <subquery2> we do SJM lookup. + So for the SJM table there would be a ref access created on the condition + t2.a=it.b. But as one can see table t2 is inside the nest and the + condition t2.a=it.b can only be evaluated in the post nest creation + context, so we need to substitute t2.a with the corresponding field item + of the nest. + + For example: + If we had a sort nest on t1,t2 the the condition t2.a = it.b will be + evaluated in the POST ORDER BY context, so t2.a should refer to the + field item of the sort nest. + +*/ + +void JOIN::substitutions_for_sjm_lookup(JOIN_TAB *sjm_tab, + Mat_join_tab_nest_info* nest_info) +{ + JOIN_TAB *tab= sjm_tab->bush_children->start; + TABLE_LIST *emb_sj_nest= tab->table->pos_in_table_list->embedding; + + /* + @see setup_sj_materialization_part1 + */ + while (!emb_sj_nest->sj_mat_info) + emb_sj_nest= emb_sj_nest->embedding; + SJ_MATERIALIZATION_INFO *sjm= emb_sj_nest->sj_mat_info; + + if (!sjm->is_sj_scan) + { + Item *left_expr= emb_sj_nest->sj_subq_pred->left_expr; + left_expr= left_expr->transform(thd, &Item::replace_with_nest_items, + TRUE, (uchar *) nest_info); + left_expr->update_used_tables(); + emb_sj_nest->sj_subq_pred->left_expr= left_expr; + } +} + + +/* + @brief + Extract from the WHERE clause the sub-condition for tables inside the nest. + + @param nest_info Structure holding information about the nest + + @details + Extract the sub-condition from the WHERE clause that can be added to the + tables inside the nest. + + Example + SELECT * from t1,t2,t3 + WHERE t1.a > t2.a (1) + AND t2.b = t3.b (2) + ORDER BY t1.a,t2.a + LIMIT 5; + + let's say in this case the join order is t1,t2,t3 and there is a nest + on t1,t2 + + From the WHERE clause we would like to extract the condition that depends + only on the inner tables of the nest. The condition (1) here satisfies + this criteria so it would be extracted from the WHERE clause. + The extracted condition here would be t1.a > t2.a. + + The extracted condition is stored inside the Mat_join_tab_nest_info + structure. + + Also we remove the top level conjuncts of the WHERE clause that were + present in the extracted condition. + + So after removal the final results would be: + WHERE clause: t2.b = t3.b ----> condition external to the nest + extracted cond: t1.a > t2.a ----> condition internal to the nest + + @note + For the sort nest the sub-condition will be evaluated before the + ORDER BY clause is applied. +*/ + +void JOIN::extract_condition_for_the_nest(Mat_join_tab_nest_info* nest_info) +{ + DBUG_ASSERT(nest_info); + Item *orig_cond= conds; + Item *extracted_cond; + + /* + check_pushable_cond_extraction would set the flag NO_EXTRACTION_FL for + all the predicates that cannot be added to the inner tables of the nest. + */ + table_map nest_tables_map= nest_info->get_tables_map(); + conds->check_pushable_cond_extraction( + &Item::pushable_cond_checker_for_tables, + (uchar*)&nest_tables_map); + + /* + build_pushable_condition would create a sub-condition that would be + added to the inner tables of the nest. This may clone some predicates too. + */ + extracted_cond= orig_cond->build_pushable_condition(thd, TRUE); + + if (extracted_cond) + { + if (extracted_cond->fix_fields_if_needed(thd, 0)) + return; + extracted_cond->update_used_tables(); + /* + Remove from the WHERE clause the top level conjuncts that were + extracted for the inner tables of the nest + */ + orig_cond= remove_pushed_top_conjuncts(thd, orig_cond); + nest_info->set_nest_cond(extracted_cond); + } + conds= orig_cond; +} + + +/* + @brief + Propagate the Multiple Equalities for all the ORDER BY items. + + @details + Propagate the multiple equalities for the ORDER BY items. + This is needed so that we can generate different join orders + that would satisfy ordering after taking equality propagation + into consideration. + + Example + SELECT * FROM t1, t2, t3 + WHERE t1.a = t2.a AND t2.b = t3.a + ORDER BY t2.a, t3.a + LIMIT 10; + + Possible join orders which satisfy the ORDER BY clause and + which we can get after equality propagation are: + - t2, sort(t2), t3, t1 ---> substitute t3.a with t2.b + - t2, sort(t2), t1, t3 ---> substitute t3.a with t2.b + - t1, t3, sort(t1,t3), t2 ---> substitute t2.a with t1.a + - t1, t2, sort(t1,t2), t3 ---> substitute t3.a with t2.b + + So with equality propagation for ORDER BY items, we can get more + join orders that could satisfy the ORDER BY clause. +*/ + +void JOIN::propagate_equal_field_for_orderby() +{ + if (!sort_nest_possible) + return; + ORDER *ord; + for (ord= order; ord; ord= ord->next) + { + if (optimizer_flag(thd, OPTIMIZER_SWITCH_ORDERBY_EQ_PROP) && cond_equal) + { + Item *item= ord->item[0]; + /* + TODO: equality substitution in the context of ORDER BY is + sometimes allowed when it is not allowed in the general case. + We make the below call for its side effect: it will locate the + multiple equality the item belongs to and set item->item_equal + accordingly. + */ + (void)item->propagate_equal_fields(thd, + Value_source:: + Context_identity(), + cond_equal); + } + } +} + + +/* + @brief + Check whether ORDER BY items can be evaluated for a given prefix + + @param previous_tables table_map of all the tables in the prefix + of the current partial plan + + @details + Here we walk through the ORDER BY items and check if the prefix of the + join resolves the ordering. + Also we look at the multiple equalities for each item in the ORDER BY list + to see if the ORDER BY items can be resolved by the given prefix. + + Example + SELECT * FROM t1, t2, t3 + WHERE t1.a = t2.a AND t2.b = t3.a + ORDER BY t2.a, t3.a + LIMIT 10; + + Let's say the given prefix is table {t1,t3}, then this function would + return TRUE because there is an equality condition t2.a=t1.a , + so t2.a can be resolved with t1.a. Hence the given prefix {t1,t3} would + resolve the ORDER BY clause. + + @retval + TRUE ordering can be evaluated by the given prefix + FALSE otherwise + +*/ + +bool JOIN::check_join_prefix_resolves_ordering(table_map previous_tables) +{ + DBUG_ASSERT(order); + ORDER *ord; + for (ord= order; ord; ord= ord->next) + { + Item *order_item= ord->item[0]; + table_map order_tables=order_item->used_tables(); + if (!(order_tables & ~previous_tables) || + (order_item->excl_dep_on_tables(previous_tables, FALSE))) + continue; + else + return FALSE; + } + return TRUE; +} + + +/* + @brief + Check if the best plan has a sort-nest or not. + + @param + n_tables[out] set to the number of tables inside the sort-nest + nest_tables_map[out] map of tables inside the sort-nest + @details + This function walks through the JOIN::best_positions array + which holds the best plan and checks if there is prefix for + which the join planner had picked a sort-nest. + + Also this function computes a table map for tables that are inside the + sort-nest + + @retval + TRUE sort-nest present + FALSE no sort-nest present +*/ + +bool JOIN::check_if_sort_nest_present(uint* n_tables, + table_map *nest_tables_map) +{ + if (!sort_nest_possible) + return FALSE; + + uint tablenr; + table_map nest_tables= 0; + uint tables= 0; + for (tablenr=const_tables ; tablenr < table_count ; tablenr++) + { + tables++; + POSITION *pos= &best_positions[tablenr]; + if (pos->sj_strategy == SJ_OPT_MATERIALIZE || + pos->sj_strategy == SJ_OPT_MATERIALIZE_SCAN) + { + SJ_MATERIALIZATION_INFO *sjm= pos->table->emb_sj_nest->sj_mat_info; + for (uint j= 0; j < sjm->tables; j++) + { + JOIN_TAB *tab= (pos+j)->table; + nest_tables|= tab->table->map; + } + tablenr+= (sjm->tables-1); + } + else + nest_tables|= pos->table->table->map; + + if (pos->sort_nest_operation_here) + { + *n_tables= tables; + *nest_tables_map= nest_tables; + return TRUE; + } + } + return FALSE; +} + + +/* + @brief + Create a sort nest info structure + + @param n_tables number of tables inside the sort-nest + @param nest_tables_map map of top level tables inside the sort-nest + + @details + This sort-nest structure would hold all the information about the + sort-nest. + + @retval + FALSE successful in creating the sort-nest info structure + TRUE error +*/ + +bool JOIN::create_sort_nest_info(uint n_tables, table_map nest_tables_map) +{ + if (!(sort_nest_info= new Sort_nest_info(this, n_tables, nest_tables_map))) + return TRUE; + return FALSE; +} + + +void JOIN::substitute_best_fields_for_order_by_items() +{ + ORDER *ord; + /* + Substitute the ORDER by items with the best field so that equality + propagation considered during best_access_path can be used. + */ + for (ord= order; ord; ord=ord->next) + { + Item *item= ord->item[0]; + item= substitute_for_best_equal_field(thd, NO_PARTICULAR_TAB, item, + cond_equal, + map2table, true); + item->update_used_tables(); + ord->item[0]= item; + } +} + + +/* + @brief + Make the sort-nest. + + @param + nest_info Structure holding information about the nest + + @details + Setup execution structures for sort-nest materialization: + - Create the list of Items of the inner tables of the sort-nest + that are needed for the post ORDER BY computations + - Create the materialization temporary table for the sort-nest + + This function fills up the Sort_nest_info structure + + @retval + TRUE : In case of error + FALSE : Nest creation successful +*/ + +bool JOIN::make_sort_nest(Mat_join_tab_nest_info *nest_info) +{ + Field_iterator_table field_iterator; + + JOIN_TAB *j; + JOIN_TAB *tab; + + if (unlikely(thd->trace_started())) + add_sort_nest_tables_to_trace(this, nest_info); + + /* + List of field items of the tables inside the sort-nest is created for + the field items that are needed to be stored inside the temporary table + of the sort-nest. Currently Item_field objects are created for the tables + inside the sort-nest for all the fields which have bitmap read_set + set for them. + + TODO varun: + An improvement would be if to remove the fields from this + list that are completely internal to the nest because such + fields would not be used in computing expression in the post + ORDER BY context + */ + + for (j= join_tab + const_tables; j < nest_info->nest_tab; j++) + { + if (!j->bush_children) + { + TABLE *table= j->table; + field_iterator.set_table(table); + for (; !field_iterator.end_of_fields(); field_iterator.next()) + { + Field *field= field_iterator.field(); + if (!bitmap_is_set(table->read_set, field->field_index)) + continue; + Item *item; + if (!(item= field_iterator.create_item(thd))) + return TRUE; + nest_info->nest_base_table_cols.push_back(item, thd->mem_root); + } + } + else + { + TABLE_LIST *emb_sj_nest; + JOIN_TAB *child_tab= j->bush_children->start; + emb_sj_nest= child_tab->table->pos_in_table_list->embedding; + /* + @see setup_sj_materialization_part1 + */ + while (!emb_sj_nest->sj_mat_info) + emb_sj_nest= emb_sj_nest->embedding; + Item_in_subselect *item_sub= emb_sj_nest->sj_subq_pred; + SELECT_LEX *subq_select= item_sub->unit->first_select(); + List_iterator_fast<Item> li(subq_select->item_list); + Item *item; + while((item= li++)) + nest_info->nest_base_table_cols.push_back(item, thd->mem_root); + } + } + + tab= nest_info->nest_tab; + DBUG_ASSERT(!tab->table); + + uint sort_nest_elements= nest_info->nest_base_table_cols.elements; + nest_info->tmp_table_param.init(); + nest_info->tmp_table_param.bit_fields_as_long= TRUE; + nest_info->tmp_table_param.field_count= sort_nest_elements; + nest_info->tmp_table_param.force_not_null_cols= FALSE; + + const LEX_CSTRING order_nest_name= { STRING_WITH_LEN("sort-nest") }; + if (!(tab->table= create_tmp_table(thd, &nest_info->tmp_table_param, + nest_info->nest_base_table_cols, + (ORDER*) 0, + FALSE /* distinct */, + 0, /*save_sum_fields*/ + thd->variables.option_bits | + TMP_TABLE_ALL_COLUMNS, + HA_POS_ERROR /*rows_limit */, + &order_nest_name))) + return TRUE; /* purecov: inspected */ + + tab->table->map= nest_info->get_tables_map(); + nest_info->table= tab->table; + tab->type= JT_ALL; + tab->table->reginfo.join_tab= tab; + + /* + The list of temp table items created here, these are needed for the + substitution for items that would be evaluated in POST SORT NEST context + */ + field_iterator.set_table(tab->table); + List_iterator_fast<Item> li(nest_info->nest_base_table_cols); + Item *item; + for (; !field_iterator.end_of_fields() && (item= li++); + field_iterator.next()) + { + Field *field= field_iterator.field(); + Item *nest_item; + if (!(nest_item= new (thd->mem_root)Item_temptable_field(thd, field))) + return TRUE; + Item_pair *tmp_field= new Item_pair(item, nest_item); + nest_info->mapping_of_items.push_back(tmp_field, thd->mem_root); + } + + /* Setting up the scan on the temp table */ + tab->read_first_record= join_init_read_record; + tab->read_record.read_record_func= rr_sequential; + tab[-1].next_select= end_nest_materialization; + DBUG_ASSERT(!nest_info->is_materialized()); + + return FALSE; +} + + +/* + @brief + Calculate the cost of adding a sort-nest. + + @param + join_record_count the cardinality of the partial join + idx position of the joined table in the partial plan + rec_len estimate of length of the record in the sort-nest table + + @details + The calculation for the cost of the sort-nest is done here, the cost + includes three components + 1) Filling the sort-nest table + 2) Sorting the sort-nest table + 3) Reading from the sort-nest table +*/ + +double JOIN::sort_nest_oper_cost(double join_record_count, uint idx, + ulong rec_len) +{ + double cost= 0; + set_if_bigger(join_record_count, 1); + /* + The sort-nest table is not created for sorting when one does sorting + on the first non-const table. So for this case we don't need to add + the cost of filling the table. + */ + if (idx != const_tables) + cost= get_tmp_table_write_cost(thd, join_record_count, rec_len) * + join_record_count; // cost to fill temp table + + // cost to perform sorting + cost+= get_tmp_table_lookup_cost(thd, join_record_count, rec_len) + + (join_record_count == 0 ? 0 : + join_record_count * log2 (join_record_count)) * + SORT_INDEX_CMP_COST; + + /* + cost for scanning the temp table. + Picked this cost from get_delayed_table_estimates() + */ + double data_size= COST_MULT(join_record_count * fraction_output_for_nest, + rec_len); + cost+= data_size/IO_SIZE + 2; + + return cost; +} + + +/* + @brief + Calculate the number of records that would be read from the sort-nest. + + @param n_tables number of tables in the sort-nest + + @details + The number of records read from the sort-nest would be: + + cardinality(join of inner table of nest) * selectivity_of_limit; + + Here selectivity of limit is how many records we would expect in the + output. + selectivity_of_limit= limit / cardinality(join of all tables) + + This number of records is what we would also see in the EXPLAIN output + for the sort-nest in the columns "rows". + + @retval Number of records that the optimizer expects to be read from the + sort-nest +*/ + +double JOIN::calculate_record_count_for_sort_nest(uint n_tables) +{ + double sort_nest_records= 1, record_count; + JOIN_TAB *tab= join_tab + const_tables; + for (uint j= 0; j < n_tables ; j++, tab++) + { + record_count= tab->records_read * tab->cond_selectivity; + sort_nest_records= COST_MULT(sort_nest_records, record_count); + } + sort_nest_records= sort_nest_records * fraction_output_for_nest; + set_if_bigger(sort_nest_records, 1); + return sort_nest_records; +} + + +/* + @brief + Find all keys that can resolve the ORDER BY clause for a table + + @details + This function sets the flag TABLE::keys_with_ordering with all the + indexes of a table that can resolve the ORDER BY clause. +*/ + +void JOIN_TAB::find_keys_that_can_achieve_ordering() +{ + if (!join->sort_nest_possible) + return; + + table->keys_with_ordering.clear_all(); + for (uint index= 0; index < table->s->keys; index++) + { + if (table->keys_in_use_for_query.is_set(index) && + test_if_order_by_key(join, join->order, table, index)) + table->keys_with_ordering.set_bit(index); + } + table->keys_with_ordering.intersect(table->keys_in_use_for_order_by); +} + + +/* + @brief + Checks if the given prefix needs Filesort for ordering. + + @param + idx position of the joined table in the partial plan + index_used >=0 number of the index that is picked as best access + -1 no index access chosen + + @details + Here we check if a given prefix requires Filesort or index on the + first non-const table to resolve the ORDER BY clause. + + @retval + TRUE Filesort is needed + FALSE index present that satisfies the ordering +*/ + +bool JOIN_TAB::needs_filesort(uint idx, int index_used) +{ + if (idx != join->const_tables) + return TRUE; + + return !check_if_index_satisfies_ordering(index_used); +} + + +/* + @brief + Find a cheaper index that resolves ordering on the first non-const table. + + @param + tab joined table + read_time [out] cost for the best index picked if cheaper + records [out] estimate of records going to be accessed by the + index + index_used >=0 number of index used for best access + -1 no index used for best access + idx position of the joined table in the partial plan + + @details + Here we try to walk through all the indexes for the first non-const table + of a given prefix. + From these indexes we are only interested in the indexes that can resolve + the ORDER BY clause as we want to shortcut the join execution for ORDER BY + LIMIT optimization. + + For each index we are interested in we try to estimate the records we + have to read to ensure #limit records in the join output. + + Then with this estimate of records we calculate the cost of using an index + and try to find the best index for access. + If the best index found from here has a lower cost than the best access + found in best_access_path, we switch the access to use the index found + here. + + @retval + -1 no cheaper index found for ordering + >=0 cheaper index found for ordering +*/ + +int get_best_index_for_order_by_limit(JOIN_TAB *tab, + ha_rows select_limit_arg, + double *read_time, + double *records, + int index_used, + uint idx) +{ + double cardinality; + JOIN *join= tab->join; + cardinality= join->cardinality_estimate; + /** + Cases when there is no need to consider indexes that can resolve the + ORDER BY clause + + 1) Table in consideration should be the first non-const table. + 2) Query does not use the ORDER BY LIMIT optimization with sort_nest + @see sort_nest_allowed + 3) Join planner is run to get an estimate of cardinality for a join + 4) No index present that can resolve the ORDER BY clause + */ + + if (idx != join->const_tables || // (1) + !join->sort_nest_possible || // (2) + join->get_cardinality_estimate || // (3) + tab->table->keys_with_ordering.is_clear_all()) // (4) + return -1; + + THD *thd= join->thd; + Json_writer_object trace_index_for_ordering(thd); + TABLE *table= tab->table; + double save_read_time= *read_time; + double save_records= *records; + double est_records= *records; + double fanout= MY_MAX(1.0, cardinality / est_records); + int best_index=-1; + trace_index_for_ordering.add("rows_estimation", est_records); + Json_writer_array considered_indexes(thd, "considered_indexes"); + + for (uint idx= 0 ; idx < table->s->keys; idx++) + { + ha_rows select_limit= select_limit_arg; + if (!table->keys_with_ordering.is_set(idx)) + continue; + Json_writer_object possible_key(thd); + double index_scan_time; + possible_key.add("index", table->key_info[idx].name); + find_cost_of_index_with_ordering(thd, tab, table, &select_limit, + fanout, est_records, + idx, &index_scan_time, + &possible_key); + + if (index_scan_time < *read_time) + { + best_index= idx; + *read_time= index_scan_time; + *records= select_limit; + } + } + considered_indexes.end(); + + if (unlikely(thd->trace_started())) + { + trace_index_for_ordering.add("best_index", + static_cast<ulonglong>(best_index)); + trace_index_for_ordering.add("records", *records); + trace_index_for_ordering.add("best_cost", *read_time); + } + + /* + If an index already found satisfied the ordering and we picked an index + for which we choose to do index scan then revert the cost and stick + with the access picked first. + Index scan would not help in comparison with ref access. + */ + if (tab->check_if_index_satisfies_ordering(index_used)) + { + if (!table->quick_keys.is_set(static_cast<uint>(index_used))) + { + best_index= -1; + *records= save_records; + *read_time= save_read_time; + } + } + return best_index; +} + + +/* + @brief + Disallow join buffering for tables that are read after sorting is done. + + @param + tab table to check if join buffering is allowed or not + + @details + Disallow join buffering for all the tables at the top level that are read + after sorting is done. + There are 2 cases + 1) Sorting on the first non-const table + For all the tables join buffering is not allowed + 2) Sorting on a prefix of the join with a sort-nest + For the tables inside the sort-nest join buffering is allowed but + for tables outside the sort-nest join buffering is not allowed + + Also for SJM table that come after the sort-nest, join buffering is allowed + for the inner tables of the SJM. + + @retval + TRUE Join buffering is allowed + FALSE Otherwise +*/ + +bool JOIN::is_join_buffering_allowed(JOIN_TAB *tab) +{ + if (!sort_nest_info) + return TRUE; + + // no need to disable join buffering for the inner tables of SJM + if (tab->bush_root_tab) + return TRUE; + + if (tab->table->map & sort_nest_info->get_tables_map()) + return TRUE; + return FALSE; +} + + +/* + @brief + Check if an index on a table resolves the ORDER BY clause. + + @param + index_used index to be checked + + @retval + TRUE index resolves the ORDER BY clause + FALSE otherwise +*/ + +bool JOIN_TAB::check_if_index_satisfies_ordering(int index_used) +{ + /* + index_used is set to + -1 for Table Scan + MAX_KEY for HASH JOIN + >=0 for ref/range/index access + */ + if (index_used < 0 || index_used == MAX_KEY) + return FALSE; + + if (table->keys_with_ordering.is_set(static_cast<uint>(index_used))) + return TRUE; + return FALSE; +} + + +/* + @brief + Set up range scan for the table. + + @param + tab table for which range scan needs to be setup + idx index for which range scan needs to created + records estimate of records to be read with range scan + + @details + Range scan is setup here for an index that can resolve the ORDER BY clause. + There are 2 cases here: + 1) If the range scan is on the same index for which we created + QUICK_SELECT when we ran the range optimizer earlier, then we try + to reuse it. + 2) The range scan is on a different index then we need to create + QUICK_SELECT for the new key. This is done by running the range + optimizer again. + + Also here we take into account if the ordering is in reverse direction. + For DESCENDING we try to reverse the QUICK_SELECT. + + @note + This is done for the ORDER BY LIMIT optimization. We try to force creation + of range scan for an index that the join planner picked for us. Also here + we reverse the range scan if the ordering is in reverse direction. +*/ + +void JOIN::setup_range_scan(JOIN_TAB *tab, uint idx, double records) +{ + SQL_SELECT *sel= NULL; + Item **sargable_cond= get_sargable_cond(this, tab->table); + int err, rc, direction; + uint used_key_parts; + key_map keymap_for_range; + Json_writer_array forcing_range(thd, "range_scan_for_order_by_limit"); + + sel= make_select(tab->table, const_table_map, const_table_map, + *sargable_cond, (SORT_INFO*) 0, 1, &err); + if (!sel) + goto use_filesort; + + /* + If the table already had a range access, check if it is the same as the + one we wanted to create range scan for, if yes don't run the range + optimizer again. + */ + + if (!(tab->quick && tab->quick->index == idx)) + { + /* Free the QUICK_SELECT that was built earlier. */ + delete tab->quick; + tab->quick= NULL; + + keymap_for_range.clear_all(); // Force the creation of quick select + keymap_for_range.set_bit(idx); // only for index using range access. + + rc= sel->test_quick_select(thd, keymap_for_range, + (table_map) 0, + (ha_rows) HA_POS_ERROR, + true, false, true, true); + if (rc <= 0) + goto use_filesort; + } + else + sel->quick= tab->quick; + + direction= test_if_order_by_key(this, order, tab->table, idx, + &used_key_parts); + + if (direction == -1) + { + /* + QUICK structure is reversed here as the ordering is in DESC order + */ + QUICK_SELECT_I *reverse_quick; + if (sel && sel->quick) + { + reverse_quick= sel->quick->make_reverse(used_key_parts); + if (!reverse_quick) + goto use_filesort; + sel->set_quick(reverse_quick); + } + } + + tab->quick= sel->quick; + + /* + Fix for explain, the records here should be set to the value + which was stored in the JOIN::best_positions object. This is needed + because the estimate of rows to be read for the first non-const table had + taken selectivity of limit into account. + */ + if (sort_nest_possible && records < tab->quick->records) + tab->quick->records= records; + + sel->quick= NULL; + +use_filesort: + delete sel; +} + + +/* + @brief + Setup range/index scan to resolve ordering on the first non-const table. + + @param + index index for which index scan or range scan needs to be setup + + @details + Here we try to prepare range scan or index scan for an index that can be + used to resolve the ORDER BY clause. This is used only for the first + non-const table of the join. + + For range scan + There is a separate call to setup_range_scan, where the QUICK_SELECT is + created for range access. In case we are not able to create a range + access, we switch back to use Filesort on the first table. + see @setup_range_scan + For index scan + We just store the index in Sort_nest_info::index_used. +*/ + +void JOIN::setup_index_use_for_ordering(int index) +{ + DBUG_ASSERT(sort_nest_info->index_used == -1); + + sort_nest_info->nest_tab= join_tab + const_tables; + POSITION *cur_pos= &best_positions[const_tables]; + JOIN_TAB *tab= cur_pos->table; + + if (cur_pos->key) + return; + + index= (index == -1) ? + (cur_pos->table->quick ? cur_pos->table->quick->index : -1) : + index; + + if (tab->check_if_index_satisfies_ordering(index)) + { + if (tab->table->quick_keys.is_set(index)) + { + // Range scan + setup_range_scan(tab, index, cur_pos->records_read); + sort_nest_info->index_used= -1; + } + else + { + // Index scan + if (tab->quick) + { + delete tab->quick; + tab->quick= NULL; + } + sort_nest_info->index_used= index; + } + } +} + + +/* + @brief + Get index used to access the table, if present + + @retval + >=0 index used to access the table + -1 no index used to access table, probably table scan is done +*/ + +int JOIN_TAB::get_index_on_table() +{ + int idx= -1; + + if (type == JT_REF || type == JT_EQ_REF || type == JT_REF_OR_NULL) + idx= ref.key; + else if (type == JT_NEXT) + idx= index; + else if (type == JT_ALL && select && select->quick) + idx= select->quick->index; + return idx; +} + + +/* + @brief + Calculate the selectivity of limit. + + @details + The selectivity of limit is calculated as + selecitivity_of_limit= rows_in_limit / cardinality_of_join + + @note + The selectivity that we get is used to make an estimate of rows + that we would read from the partial join of the tables inside the + sort-nest. +*/ + +void JOIN::set_fraction_output_for_nest() +{ + if (sort_nest_possible && !get_cardinality_estimate) + { + fraction_output_for_nest= select_limit < cardinality_estimate ? + select_limit / cardinality_estimate : + 1.0; + if (unlikely(thd->trace_started())) + { + Json_writer_object trace_limit(thd); + trace_limit.add("cardinality", cardinality_estimate); + trace_limit.add("selectivity_of_limit", fraction_output_for_nest*100); + } + } +} + + +/* + @brief + Sort nest is allowed when one can shortcut the join execution. + + @details + For all the operations where one requires entire join computation to be + done first and then apply the operation on the join output, + such operations can't make use of the sort-nest. + So this function disables the use of sort-nest for such operations. + + Sort nest is not allowed for + 1) No ORDER BY clause + 2) Only constant tables in the join + 3) DISTINCT CLAUSE + 4) GROUP BY CLAUSE + 5) HAVING clause + 6) Aggregate Functions + 7) Window Functions + 8) Using ROLLUP + 9) Using SQL_BUFFER_RESULT + 10) LIMIT is absent + 11) Only SELECT queries can use the sort nest + + @retval + TRUE Sort-nest is allowed + FALSE Otherwise + +*/ + +bool JOIN::sort_nest_allowed() +{ + return thd->variables.use_sort_nest && order && + !(const_tables == table_count || + (select_distinct || group_list) || + having || + MY_TEST(select_options & OPTION_BUFFER_RESULT) || + (rollup.state != ROLLUP::STATE_NONE && select_distinct) || + select_lex->window_specs.elements > 0 || + select_lex->agg_func_used() || + select_limit == HA_POS_ERROR || + thd->lex->sql_command != SQLCOM_SELECT); +} + + +/* + @brief + Consider adding a sort-nest on a prefix of the join + + @param prefix_tables map of all the tables in the prefix + + @details + This function is used during the join planning stage, where the join + planner decides if it can add a sort-nest on a prefix of a join. + The join planner does not add the sort-nest in the following cases: + 1) Queries where adding a sort-nest is not possible. + see @sort_nest_allowed + 2) Join planner is run to get the cardinality of the join + 3) All inner tables of an outer join are inside the nest or outside + 4) All inner tables of a semi-join are inside the nest or outside + 5) Given prefix cannot resolve the ORDER BY clause + + @retval + TRUE sort-nest can be added on a prefix of a join + FALSE otherwise +*/ + +bool JOIN::consider_adding_sort_nest(table_map prefix_tables) +{ + if (!sort_nest_possible || // (1) + get_cardinality_estimate || // (2) + cur_embedding_map || // (3) + cur_sj_inner_tables) // (4) + return FALSE; + + return check_join_prefix_resolves_ordering(prefix_tables); // (5) +} + + +/* + @brief + Check if indexes on a table are allowed to resolve the ORDER BY clause + + @param + idx position of the table in the partial plan + + @retval + TRUE Indexes are allowed to resolve ORDER BY clause + FALSE Otherwise + +*/ + +bool JOIN::is_index_with_ordering_allowed(uint idx) +{ + /* + An index on a table can allowed to resolve ordering in these cases: + 1) Table should be the first non-const table + 2) Query that allows the ORDER BY LIMIT optimization. + @see sort_nest_allowed + 3) Join planner is not run to get the estimate of cardinality + */ + return idx == const_tables && // (1) + sort_nest_possible && // (2) + !get_cardinality_estimate; // (3) +} + +/* + @brief + Find the cost to access a table with an index that can resolve ORDER BY. + + @param + THD thread structure + tab join_tab structure for joined table + table first non-const table + select_limit_arg limit for the query + fanout fanout of the join + est_best_records estimate of records for best access + nr index number + index_scan_time[out] cost to access the table with the + the index +*/ + +void find_cost_of_index_with_ordering(THD *thd, const JOIN_TAB *tab, + TABLE *table, + ha_rows *select_limit_arg, + double fanout, double est_best_records, + uint nr, double *index_scan_time, + Json_writer_object *trace_possible_key) +{ + KEY *keyinfo= table->key_info + nr; + ha_rows select_limit= *select_limit_arg; + double rec_per_key; + double table_records= table->stat_records(); + /* + If tab=tk is not the last joined table tn then to get first + L records from the result set we can expect to retrieve + only L/fanout(tk,tn) where fanout(tk,tn) says how many + rows in the record set on average will match each row tk. + Usually our estimates for fanouts are too pessimistic. + So the estimate for L/fanout(tk,tn) will be too optimistic + and as result we'll choose an index scan when using ref/range + access + filesort will be cheaper. + */ + select_limit= (ha_rows) (select_limit < fanout ? + 1 : select_limit/fanout); + + /* + refkey_rows_estimate is E(#rows) produced by the table access + strategy that was picked without regard to ORDER BY ... LIMIT. + + It will be used as the source of selectivity data. + Use table->cond_selectivity as a better estimate which includes + condition selectivity too. + */ + { + // we use MIN(...), because "Using LooseScan" queries have + // cond_selectivity=1 while refkey_rows_estimate has a better + // estimate. + est_best_records= MY_MIN(est_best_records, + ha_rows(table_records * table->cond_selectivity)); + } + + /* + We assume that each of the tested indexes is not correlated + with ref_key. Thus, to select first N records we have to scan + N/selectivity(ref_key) index entries. + selectivity(ref_key) = #scanned_records/#table_records = + refkey_rows_estimate/table_records. + In any case we can't select more than #table_records. + N/(refkey_rows_estimate/table_records) > table_records + <=> N > refkey_rows_estimate. + */ + + if (select_limit > est_best_records) + select_limit= table_records; + else + select_limit= (ha_rows) (select_limit * + (double) table_records / + est_best_records); + + rec_per_key= keyinfo->actual_rec_per_key(keyinfo->user_defined_key_parts-1); + set_if_bigger(rec_per_key, 1); + /* + Here we take into account the fact that rows are + accessed in sequences rec_per_key records in each. + Rows in such a sequence are supposed to be ordered + by rowid/primary key. When reading the data + in a sequence we'll touch not more pages than the + table file contains. + TODO. Use the formula for a disk sweep sequential access + to calculate the cost of accessing data rows for one + index entry. + */ + *index_scan_time= select_limit/rec_per_key * + MY_MIN(rec_per_key, table->file->scan_time()); + + if (unlikely(thd->trace_started())) + { + trace_possible_key->add("updated_limit", select_limit); + trace_possible_key->add("index_scan_time", *index_scan_time); + } + + double range_scan_time; + if (get_range_limit_read_cost(tab, table, table_records, nr, + select_limit, &range_scan_time)) + { + trace_possible_key->add("range_scan_time", range_scan_time); + if (range_scan_time < *index_scan_time) + *index_scan_time= range_scan_time; + } + *select_limit_arg= select_limit; +} + diff --git a/sql/sql_tvc.cc b/sql/sql_tvc.cc index 678dd81709e..a268b5f358d 100644 --- a/sql/sql_tvc.cc +++ b/sql/sql_tvc.cc @@ -1063,9 +1063,8 @@ bool JOIN::transform_in_predicates_into_in_subq(THD *thd) { select_lex->parsing_place= IN_WHERE; conds= - conds->transform(thd, - &Item::in_predicate_to_in_subs_transformer, - (uchar*) 0); + conds->transform(thd, &Item::in_predicate_to_in_subs_transformer, + FALSE, (uchar*) 0); if (!conds) DBUG_RETURN(true); select_lex->prep_where= conds ? conds->copy_andor_structure(thd) : 0; @@ -1084,7 +1083,7 @@ bool JOIN::transform_in_predicates_into_in_subq(THD *thd) { table->on_expr= table->on_expr->transform(thd, - &Item::in_predicate_to_in_subs_transformer, + &Item::in_predicate_to_in_subs_transformer, FALSE, (uchar*) 0); if (!table->on_expr) DBUG_RETURN(true); diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index ab382304973..065be5caf20 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -6438,3 +6438,9 @@ static Sys_var_ulonglong Sys_max_rowid_filter_size( SESSION_VAR(max_rowid_filter_size), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1024, (ulonglong)~(intptr)0), DEFAULT(128*1024), BLOCK_SIZE(1)); + +static Sys_var_mybool Sys_use_sort_nest( + "use_sort_nest", + "Enable the sort nest", + SESSION_VAR(use_sort_nest), CMD_LINE(OPT_ARG), + DEFAULT(FALSE)); diff --git a/sql/table.h b/sql/table.h index acaa8bcdafe..616f7657411 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1414,6 +1414,8 @@ public: */ SplM_opt_info *spl_opt_info; key_map keys_usable_for_splitting; + /* Map of keys that can be used to resolve the ORDER BY clause */ + key_map keys_with_ordering; /* Conjunction of the predicates of the form IS NOT NULL(f) where f refers to |