UNION vs UNION ALL
Comments enabled. I *really* need your comment
In my previous article:
I described efficient ORDER BY along with LIMIT for UNION'ed resultsets.
One thing left unanswered: why UNION DISTINCT in some cases is faster than UNION ALL?
UNION DISTINCT (for which a mere UNION is an alias) implies selecting DISTINCT values from the resultsets, while UNION ALL just selects all values, including duplicates.
On one hand, UNION ALL does not perform duplicate checking, which should be faster (and almost always is). On the other hand, it returns more rows.
If some complex calculations are performed on these rows, this may kill all performance benefits.
Let's create the sample tables:
CREATE TABLE t_a (
id INT NOT NULL PRIMARY KEY,
orderer INT NOT NULL,
value VARCHAR(50) NOT NULL,
KEY ix_a_orderer (orderer)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE t_b (
id INT NOT NULL PRIMARY KEY,
orderer INT NOT NULL,
value VARCHAR(50) NOT NULL,
KEY ix_b_orderer (orderer)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DELIMITER $$
CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
DECLARE _cnt INT;
SET _cnt = 1;
WHILE _cnt <= cnt DO
INSERT
INTO filler
SELECT _cnt;
SET _cnt = _cnt + 1;
END WHILE;
END
$$
DELIMITER ;
START TRANSACTION;
CALL prc_filler(200000);
COMMIT;
INSERT
INTO t_a (id, orderer, value)
SELECT id, RAND(20090516) * 100, CONCAT('Value A ', id)
FROM filler;
INSERT
INTO t_b (id, orderer, value)
SELECT id, RAND(20090516 << 1) * 100, CONCAT('Value B ', id)
FROM filler;
[/sourcecode]
We have 200,000 rows in each tables, and randomly filled <code>orderer</code> fields, which take values from <strong>0</strong> to <strong>99</strong>.
Now, let's look into two queries:
SELECT orderer % 10
FROM t_a
UNION ALL
SELECT orderer % 10
FROM t_b
ORDER BY
1
LIMIT 10
| orderer % 10 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 rows fetched in 0.0002s (0.5396s) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | t_a | index | ix_a_orderer | 4 | 200007 | 100.00 | Using index | ||
| 2 | UNION | t_b | index | ix_b_orderer | 4 | 200834 | 100.00 | Using index | ||
| UNION RESULT | <union1,2> | ALL | Using filesort |
SELECT orderer % 10
FROM t_a
UNION
SELECT orderer % 10
FROM t_b
ORDER BY
1
LIMIT 10
| orderer % 10 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 7 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 8 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 9 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 rows fetched in 0.0002s (0.4678s) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | t_a | index | ix_a_orderer | 4 | 200007 | 100.00 | Using index | ||
| 2 | UNION | t_b | index | ix_b_orderer | 4 | 200834 | 100.00 | Using index | ||
| UNION RESULT | <union1,2> | ALL | Using filesort |
We see that the UNION query is apparently faster than a UNION ALL one.
That's because there are no so many DISTINCT values (only 10) and eliminating the duplicates is almost free, while sorting is performed on a much smaller recordset.
The previous article described how to improve the UNION ALL query.
Now, we can improve the UNION query.
We have a deterministic expression in the SELECT clause. That means that the expression gives same results for same input values.
So if we calculate a list of distinct input values beforehand, this will not affect our recordset, because equal values will give us equal results anyway. Doing this is much easier since we have an index on orderer.
Then we just repeat the UNION to get rid of duplicates that appeared after we calculated the expressions, but this time there will be much fewer expressions to calculate (which is good by itself), and much fewer duplicates to eliminate.
SELECT DISTINCT orderer % 10
FROM (
SELECT DISTINCT orderer
FROM t_a
UNION
SELECT DISTINCT orderer
FROM t_b
) q
ORDER BY
1
LIMIT 10
| orderer % 10 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 7 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 8 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 9 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 rows fetched in 0.0002s (0.1974s) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | <derived2> | ALL | 101 | 100.00 | Using temporary; Using filesort | ||||
| 2 | DERIVED | t_a | index | ix_a_orderer | 4 | 199875 | 100.00 | Using index | ||
| 3 | UNION | t_b | index | ix_b_orderer | 4 | 200046 | 100.00 | Using index | ||
| UNION RESULT | <union2,3> | ALL |
Now, the DISTINCT's are precalculated, and this helped to reduce the query time from 0.47 to less than 0.20 seconds.
Subscribe in a reader