I had posted a question about mysql equality range optimization here.
At this post, I want to discuss about why mysql optimizer is too bad for this type of query: col_name IN (val1, ..., valN)
.
My table:
alert_user(id, user_id, source_id, start_time, is_read, ...)
Indexes:
idx_user_start(user_id, start_time)
idx_user_read_start(user_id, is_read, start_time)
idx_user_source_start(user_id, source_id, start_time)
idx_user_read_source_start(user_id, is_read, source_id, start_time)
Records: 30M
Records of user_id 1: 6M
1. Query 1
SELECT id, source_id, is_read, start_time
FROM alert_user
WHERE user_id = 1 AND source_id IN (3, 48)
ORDER BY start_time DESC LIMIT 100;
Run: 170s
- access_type: index
- key: idx_user_start
- key_length: 12
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "1316696.93"
},
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "alert_user",
"access_type": "index",
"possible_keys": [
"idx_user_source_start",
"idx_user_start",
"idx_user_read_start",
"idx_user_read_source_start"
],
"key": "idx_user_start",
"used_key_parts": [
"user_id",
"start_time"
],
"key_length": "12",
"rows_examined_per_scan": 314,
"rows_produced_per_join": 2540666,
"filtered": "4.57",
"backward_index_scan": true,
"cost_info": {
"read_cost": "46363.53",
"eval_cost": "254066.68",
"prefix_cost": "1316696.93",
"data_read_per_join": "193M"
},
"used_columns": [
"id",
"user_id",
"is_read",
"start_time",
"source_id"
],
"attached_condition": "((`test2`.`alert_user`.`user_id` = 1) and (`test2`.`alert_user`.`source_id` in (3,48)))"
}
}
}
}
2. Query 2
SELECT id, source_id, is_read, start_time
FROM alert_user use index (idx_user_source_start)
WHERE (user_id = 1 AND source_id IN (3, 48))
ORDER BY start_time DESC LIMIT 100;
Run: 3s
- access_type: range
- key: idx_user_source_start
- key_length: 12
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "2142293.68"
},
"ordering_operation": {
"using_filesort": true,
"table": {
"table_name": "alert_user",
"access_type": "range",
"possible_keys": [
"idx_user_source_start"
],
"key": "idx_user_source_start",
"used_key_parts": [
"user_id",
"source_id"
],
"key_length": "12",
"rows_examined_per_scan": 1826304,
"rows_produced_per_join": 1826304,
"filtered": "100.00",
"index_condition": "((`test2`.`alert_user`.`user_id` = 1) and (`test2`.`alert_user`.`source_id` in (3,48)))",
"using_MRR": true,
"cost_info": {
"read_cost": "1959663.28",
"eval_cost": "182630.40",
"prefix_cost": "2142293.68",
"data_read_per_join": "139M"
},
"used_columns": [
"id",
"user_id",
"is_read",
"start_time",
"source_id"
]
}
}
}
}
3. Query 3
I deleted 2 indexes which contain is_read, and run query
SELECT id, source_id, is_read, start_time
FROM alert_user
WHERE (user_id = 1 AND source_id IN (3, 48))
ORDER BY start_time DESC LIMIT 100;
Run: < 1s
- access_type: ref
- key: idx_user_start
- key_length: 4
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "1903471.80"
},
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "alert_user",
"access_type": "ref",
"possible_keys": [
"idx_user_source_start",
"idx_user_start"
],
"key": "idx_user_start",
"used_key_parts": [
"user_id"
],
"key_length": "4",
"ref": [
"const"
],
"rows_examined_per_scan": 11740588,
"rows_produced_per_join": 2348117,
"filtered": "20.00",
"backward_index_scan": true,
"cost_info": {
"read_cost": "729413.00",
"eval_cost": "234811.76",
"prefix_cost": "1903471.80",
"data_read_per_join": "179M"
},
"used_columns": [
"id",
"user_id",
"is_read",
"start_time",
"source_id"
],
"attached_condition": "(`test2`.`alert_user`.`source_id` in (3,48))"
}
}
}
}
Mysql uses 3 different execution strategies for the above 3 queries. All are slow. I know 2 methods to optimize the above query:
- Use UNION (< 400ms)
- Use Lateral JOIN (< 400ms)
However, I still have to hint mysql use index idx_user_source_start. Maybe creating many indexes makes mysql cannot choose the optimal index.
Second, I feel a bit tired because I have to rewriting SQL to UNION when query contain IN (val1, val2,...).
Have anyone faced this problem in MySQL, please share your thoughts.