I have a table "client_data" with 2 indexes:
- CLIENT_USER_CREATED (CLIENT, USER, CREATED)
- CLIENT_CREATED (CLIENT, CREATED)
The primary key is (ID, CLIENT, CREATED)
The table is partitioned by range on CREATED column by months: p202009, p202010, p202011, p202012, p202101, etc. Each partition has around 40M rows. The entire table has more than 1B rows.
I have a query:
SELECT * FROM client_data p
WHERE p.CLIENT IN (1001) AND p.CREATED >= '2020-10-01 00:00:00'
ORDER BY p.CREATED DESC
LIMIT 100 OFFSET 400
When I run the query with LIMIT and OFFSET, MySQL uses the first index, which is not optimal (takes 3 sec).
When I run the same query without OFFSET, MySQL uses the second index, which is optimal (takes 5 ms).
Here are the plans (classic + tree views):
-- the "bad" CLIENT_USER_CREATED index is used
EXPLAIN SELECT * FROM client_data p WHERE p.CLIENT IN (1001) AND p.CREATED >= '2020-10-01 00:00:00' ORDER BY p.CREATED DESC LIMIT 100 OFFSET 400
id;select_type;table;partitions;type;possible_keys;key;key_len;ref;rows;filtered;Extra
1 SIMPLE p p202010,p202011,p202012,p202101,p202102,p202103,p202104,p202105,p202106,p202107,p202108,p202109,p202110,p202111,p202112,p202201,p202202,p202203,p202204,p202205,p202206,p202207,p202208,p202209,p202210,p202211,p202212,p202301,p202302,p202303,p202304,p202305,p202306,p202307,p202308,p202309,p202310,p202311,p202312,pmax ref CLIENT_CREATED,CLIENT_USER_CREATED CLIENT_USER_CREATED 4 const 657814 50.0 Using index condition; Using filesort
-> Limit/Offset: 100/400 row(s) (cost=690705 rows=100) (actual time=3330..3331 rows=100 loops=1)
-> Sort: p.CREATED DESC, limit input to 500 row(s) per chunk (cost=690705 rows=657814) (actual time=3325..3331 rows=500 loops=1)
-> Index lookup on p using CLIENT_USER_CREATED (CLIENT=1001), with index condition: (p.CREATED >= TIMESTAMP'2020-10-01 00:00:00') (cost=690705 rows=657814) (actual time=0.0653..2477 rows=365293 loops=1)
-- force using "good" CLIENT_CREATED index
EXPLAIN SELECT * FROM client_data p USE INDEX (CLIENT_CREATED) WHERE p.CLIENT IN (1001) AND p.CREATED >= '2020-10-01 00:00:00' ORDER BY p.CREATED DESC LIMIT 100
id;select_type;table;partitions;type;possible_keys;key;key_len;ref;rows;filtered;Extra
1 SIMPLE p p202010,p202011,p202012,p202101,p202102,p202103,p202104,p202105,p202106,p202107,p202108,p202109,p202110,p202111,p202112,p202201,p202202,p202203,p202204,p202205,p202206,p202207,p202208,p202209,p202210,p202211,p202212,p202301,p202302,p202303,p202304,p202305,p202306,p202307,p202308,p202309,p202310,p202311,p202312,pmax range CLIENT_CREATED CLIENT_CREATED 9 607747 100.0 Using index condition; Backward index scan
-> Limit: 100 row(s) (cost=729297 rows=100) (actual time=1.16..2.75 rows=100 loops=1)
-> Index range scan on p using CLIENT_CREATED over (CLIENT = 1001 AND '2020-10-01 00:00:00' <= CREATED) (reverse), with index condition: ((p.CLIENT = 1001) and (p.CREATED >= TIMESTAMP'2020-10-01 00:00:00')) (cost=729297 rows=607747) (actual time=1.16..2.74 rows=100 loops=1)
-- the "good" CLIENT_CREATED index is used after I remove OFFSET
EXPLAIN analyze SELECT * FROM client_data p WHERE p.CLIENT IN (1001) AND p.CREATED >= '2020-10-01 00:00:00' ORDER BY p.CREATED DESC LIMIT 100
id;select_type;table;partitions;type;possible_keys;key;key_len;ref;rows;filtered;Extra
1 SIMPLE p p202010,p202011,p202012,p202101,p202102,p202103,p202104,p202105,p202106,p202107,p202108,p202109,p202110,p202111,p202112,p202201,p202202,p202203,p202204,p202205,p202206,p202207,p202208,p202209,p202210,p202211,p202212,p202301,p202302,p202303,p202304,p202305,p202306,p202307,p202308,p202309,p202310,p202311,p202312,pmax range CLIENT_CREATED,CLIENT_USER_CREATED CLIENT_CREATED 9 607747 100.0 Using index condition; Backward index scan
-> Limit: 100 row(s) (cost=1.33e+6 rows=100) (actual time=86.3..87.5 rows=100 loops=1)
-> Index range scan on p using CLIENT_CREATED over (CLIENT = 1001 AND '2020-10-01 00:00:00' <= CREATED) (reverse), with index condition: ((p.CLIENT = 1001) and (p.CREATED >= TIMESTAMP'2020-10-01 00:00:00')) (cost=1.33e+6 rows=607747) (actual time=86.3..87.5 rows=100 loops=1)
The CLIENT column has around 1000 uniq values.
The version of MySQL is 8.0.33.
I wonder why MySQL does not use an optimal index for the query, which satisfies the leftmost prefix requirement, searching for only one CLIENT, and sorting by the second index field then.
WHERE p.CLIENT = 1001 AND p.CREATED >= '2020-10-01 00:00:00'
(use=
instead ofIN()
), does it choose the better index?=
instead ofIN()
it still uses the CLIENT_USER_CREATED index.