在MySQL里,如果order by + limit,where或join条件还有可选索引,有可能走错索引,默认的是偏向于走order by 列索引。
结构如下:
-
show create table empm\G 这个可以看索引创建的先后顺序
-
*************************** 1. row ***************************
-
Table: empm
-
Create Table: CREATE TABLE `empm` (
-
`emp_no` int NOT NULL,
-
`birth_date` date NOT NULL,
-
`first_name` varchar(100) CHARACTER SET utf8mb3 NOT NULL,
-
`last_name` varchar(16) CHARACTER SET utf8mb3 DEFAULT NULL,
-
`gender` enum('M','F') CHARACTER SET utf8mb3 NOT NULL,
-
`hire_date` date NOT NULL,
-
`upper_first_name` varchar(100) CHARACTER SET utf8mb3 DEFAULT NULL,
-
PRIMARY KEY (`emp_no`),
-
KEY `idx_empm` (`first_name`,`last_name`),
-
KEY `idx1_empm` (`first_name`),
-
KEY `idx2_empm` (`first_name`,`last_name`,`hire_date`),
-
KEY `idx3_empm` (`first_name`,`hire_date`)
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
-
1 row in set (0.01 sec)
-
-
-
*************************** 1. row ***************************
-
Name: empm
-
Engine: InnoDB
-
Version: 10
-
Row_format: Dynamic
-
Rows: 199422
-
Avg_row_length: 65
-
Data_length: 13107200
-
Max_data_length: 0
-
Index_length: 23920640
-
Data_free: 5242880
-
Auto_increment: NULL
-
Create_time: 2024-12-18 17:15:17
-
Update_time: NULL
-
Check_time: NULL
-
Collation: utf8mb4_0900_ai_ci
-
Checksum: NULL
-
Create_options:
-
Comment:
-
1 row in set (0.00 sec)
-
-
索引如下:
-
show index from empm; 这个可以看索引创建的先后顺序
-
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
-
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
-
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
-
| empm | 0 | PRIMARY | 1 | emp_no | A | 199422 | NULL | NULL | | BTREE | | | YES | NULL |
-
| empm | 1 | idx_empm | 1 | first_name | A | 1260 | NULL | NULL | | BTREE | | | YES | NULL |
-
| empm | 1 | idx_empm | 2 | last_name | A | 147521 | NULL | NULL | YES | BTREE | | | YES | NULL |
-
| empm | 1 | idx1_empm | 1 | first_name | A | 1334 | NULL | NULL | | BTREE | | | YES | NULL |
-
| empm | 1 | idx2_empm | 1 | first_name | A | 1299 | NULL | NULL | | BTREE | | | YES | NULL |
-
| empm | 1 | idx2_empm | 2 | last_name | A | 143264 | NULL | NULL | YES | BTREE | | | YES | NULL |
-
| empm | 1 | idx2_empm | 3 | hire_date | A | 199422 | NULL | NULL | | BTREE | | | YES | NULL |
-
| empm | 1 | idx3_empm | 1 | first_name | A | 1269 | NULL | NULL | | BTREE | | | YES | NULL |
-
| empm | 1 | idx3_empm | 2 | hire_date | A | 156396 | NULL | NULL | | BTREE | | | YES | NULL |
-
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
-
9 rows in set (0.00 sec)
-
-
这个不能看索引创建的先后顺序:
-
select * from mysql.innodb_index_stats where table_name='empm';
-
+---------------+------------+------------+---------------------+--------------+------------+-------------+---------------------------------------+
-
| database_name | table_name | index_name | last_update | stat_name | stat_value | sample_size | stat_description |
-
+---------------+------------+------------+---------------------+--------------+------------+-------------+---------------------------------------+
-
| employees | empm | PRIMARY | 2024-12-18 17:15:23 | n_diff_pfx01 | 199422 | 20 | emp_no |
-
| employees | empm | PRIMARY | 2024-12-18 17:15:23 | n_leaf_pages | 691 | NULL | Number of leaf pages in the index |
-
| employees | empm | PRIMARY | 2024-12-18 17:15:23 | size | 800 | NULL | Number of pages in the index |
-
| employees | empm | idx1_empm | 2024-12-18 17:15:23 | n_diff_pfx01 | 1334 | 20 | first_name |
-
| employees | empm | idx1_empm | 2024-12-18 17:15:23 | n_diff_pfx02 | 189886 | 20 | first_name,emp_no |
-
| employees | empm | idx1_empm | 2024-12-18 17:15:23 | n_leaf_pages | 203 | NULL | Number of leaf pages in the index |
-
| employees | empm | idx1_empm | 2024-12-18 17:15:23 | size | 288 | NULL | Number of pages in the index |
-
| employees | empm | idx2_empm | 2024-12-18 17:15:23 | n_diff_pfx01 | 1299 | 20 | first_name |
-
| employees | empm | idx2_empm | 2024-12-18 17:15:23 | n_diff_pfx02 | 143264 | 20 | first_name,last_name |
-
| employees | empm | idx2_empm | 2024-12-18 17:15:23 | n_diff_pfx03 | 200569 | 20 | first_name,last_name,hire_date |
-
| employees | empm | idx2_empm | 2024-12-18 17:15:23 | n_diff_pfx04 | 199408 | 20 | first_name,last_name,hire_date,emp_no |
-
| employees | empm | idx2_empm | 2024-12-18 17:15:23 | n_leaf_pages | 352 | NULL | Number of leaf pages in the index |
-
| employees | empm | idx2_empm | 2024-12-18 17:15:23 | size | 417 | NULL | Number of pages in the index |
-
| employees | empm | idx3_empm | 2024-12-18 17:15:23 | n_diff_pfx01 | 1269 | 20 | first_name |
-
| employees | empm | idx3_empm | 2024-12-18 17:15:23 | n_diff_pfx02 | 156396 | 20 | first_name,hire_date |
-
| employees | empm | idx3_empm | 2024-12-18 17:15:23 | n_diff_pfx03 | 199752 | 20 | first_name,hire_date,emp_no |
-
| employees | empm | idx3_empm | 2024-12-18 17:15:23 | n_leaf_pages | 240 | NULL | Number of leaf pages in the index |
-
| employees | empm | idx3_empm | 2024-12-18 17:15:23 | size | 289 | NULL | Number of pages in the index |
-
| employees | empm | idx_empm | 2024-12-18 17:15:23 | n_diff_pfx01 | 1260 | 20 | first_name |
-
| employees | empm | idx_empm | 2024-12-18 17:15:23 | n_diff_pfx02 | 147521 | 20 | first_name,last_name |
-
| employees | empm | idx_empm | 2024-12-18 17:15:23 | n_diff_pfx03 | 207457 | 20 | first_name,last_name,emp_no |
-
| employees | empm | idx_empm | 2024-12-18 17:15:23 | n_leaf_pages | 342 | NULL | Number of leaf pages in the index |
-
| employees | empm | idx_empm | 2024-12-18 17:15:23 | size | 466 | NULL | Number of pages in the index |
-
+---------------+------------+------------+---------------------+--------------+------------+-------------+---------------------------------------+
-
23 rows in set (0.00 sec)
问题如下:
SQL1竟然默认走idx_empm(first_name,last_name),那么order by emp_no desc这部分就走不了索引,因为没有last_name。
应该走SQL2的所有idx1_empm(first_name)才对,因为所以默认包含主键emp_no,前导列等值,可以利用reverse扫描,消除排序。
从估算的cost看,两条SQL cost一样,都是cost=10291.95,所以按照索引谁先创建的走谁。 idx_empm先于idx1_emp创建,所以走idx_empm,
为什么cost一样呢?因为比较访问路径时候不考虑排序,可以从optimizer trace看出,这个和limit分页里丢掉order by 列是一样的问题,是BUG
###############
这个BUG主要是order by pk或order by pk desc上,在索引访问路径选择的时候没有考虑cost,然后计算order by的时候丢掉了排序列,有undefined。
单表,使用index dive计算选择率。
###########
mysql不计算访问路径不考虑排序列,等到排序优化,看能不能通过索引消除排序,直接启发式选择。
-
SQL1:
-
explain analyze select * from empm where first_name = 'Aamer' order by emp_no desc\G
-
*************************** 1. row ***************************
-
EXPLAIN: -> Sort: empm.emp_no DESC (cost=10291.95 rows=96912) (actual time=136.452..143.969 rows=50106 loops=1)
-
-> Index lookup on empm using idx_empm (first_name='Aamer') (actual time=0.070..113.065 rows=50106 loops=1)
-
-
1 row in set (0.15 sec)
-
-
SQL2:
-
explain analyze select/*+index(empm idx1_empm)*/ * from empm where first_name = 'Aamer' order by emp_no desc\G
-
*************************** 1. row ***************************
-
EXPLAIN: -> Index lookup on empm using idx1_empm (first_name='Aamer') (reverse) (cost=10291.95 rows=96912) (actual time=1.158..85.386 rows=50106 loops=1)
-
-
1 row in set (0.10 sec)
看看trace:
可以看到,因为先比较访问路径时候,没有考虑排序成本,然后idx_empm先计算的,和idx1_empm cost一样,所以选择了idx_empm,
{BANNED}最佳后考虑索引排序,发现排序字段是undefined,中间过程丢掉了,这个是mysql源代码部分丢掉了中间过程,是代码BUG。
这个BUG主要是order by pk或order by pk desc上,order by非主键不会有这个问题。
-
set optimizer_trace=1;
-
explain analyze select * from empm where first_name = 'Aamer' order by emp_no desc\G
-
select * from information_schema.optimizer_trace\G
-
-
-
{
-
"rows_estimation": [
-
{
-
"table": "`empm`",
-
"range_analysis": {
-
"table_scan": {
-
"rows": 193825,
-
"cost": 19584.8 --- 全表扫描cost
-
},
-
"potential_range_indexes": [
-
{
-
"index": "PRIMARY",
-
"usable": false,
-
"cause": "not_applicable"
-
},
-
{
-
"index": "idx_empm",
-
"usable": true,
-
"key_parts": [
-
"first_name",
-
"last_name",
-
"emp_no"
-
]
-
},
-
{
-
"index": "idx1_empm",
-
"usable": true,
-
"key_parts": [
-
"first_name",
-
"emp_no"
-
]
-
},
-
{
-
"index": "idx2_empm",
-
"usable": true,
-
"key_parts": [
-
"first_name",
-
"hire_date",
-
"emp_no"
-
]
-
}
-
],
-
"setup_range_conditions": [
-
],
-
"group_index_range": {
-
"chosen": false,
-
"cause": "not_group_by_or_distinct"
-
},
-
"skip_scan_range": {
-
"potential_skip_scan_indexes": [
-
{
-
"index": "idx_empm",
-
"usable": false,
-
"cause": "query_references_nonkey_column"
-
},
-
{
-
"index": "idx1_empm",
-
"usable": false,
-
"cause": "query_references_nonkey_column"
-
},
-
{
-
"index": "idx2_empm",
-
"usable": false,
-
"cause": "query_references_nonkey_column"
-
}
-
]
-
},
-
"analyzing_range_alternatives": {
-
"range_scan_alternatives": [
-
{
-
"index": "idx_empm",
-
"ranges": [
-
"first_name = 'Aamer'"
-
],
-
"index_dives_for_eq_ranges": true,
-
"rowid_ordered": false,
-
"using_mrr": false,
-
"index_only": false,
-
"in_memory": 1,
-
"rows": 96912,
-
"cost": 33919.5, -- 索引扫描cost >全表扫描的cost
-
"chosen": false, --不选择
-
"cause": "cost"
-
},
-
{
-
"index": "idx1_empm",
-
"ranges": [
-
"first_name = 'Aamer'"
-
],
-
"index_dives_for_eq_ranges": true,
-
"rowid_ordered": true,
-
"using_mrr": false,
-
"index_only": false,
-
"in_memory": 0.349754,
-
"rows": 96912,
-
"cost": 33919.5, -- 索引扫描cost >全表扫描的cost
-
"chosen": false, --不选择
-
"cause": "cost"
-
},
-
{
-
"index": "idx2_empm",
-
"ranges": [
-
"first_name = 'Aamer'"
-
],
-
"index_dives_for_eq_ranges": true,
-
"rowid_ordered": false,
-
"using_mrr": false,
-
"index_only": false,
-
"in_memory": 0,
-
"rows": 96912,
-
"cost": 33919.5,
-
"chosen": false,
-
"cause": "cost"
-
}
-
],
-
"analyzing_roworder_intersect": {
-
"usable": false, -- range scan没有被选择
-
"cause": "too_few_roworder_scans"
-
}
-
}
-
}
-
}
-
]
-
},
-
{
-
"considered_execution_plans": [
-
{
-
"plan_prefix": [
-
],
-
"table": "`empm`",
-
"best_access_path": {
-
"considered_access_paths": [
-
{
-
"access_type": "ref", --- 计算ref
-
"index": "idx_empm",
-
"rows": 96912,
-
"cost": 10292, -- 先计算的idx_empm,cost=10292,比全表扫描的小,所以走idx_empm的ref扫描
-
"chosen": true -- 选择这个访问路径,问题是排序成本没有加上去
-
},
-
{
-
"access_type": "ref",
-
"index": "idx1_empm",
-
"rows": 96912,
-
"cost": 10292, -- idx1_empm的ref扫描,cost和idx_empm一样,所以不用,这里其实因为idx_empm ref,
-
"chosen": false --问题是排序成本没有加上去
-
},
-
{
-
"access_type": "ref",
-
"index": "idx2_empm",
-
"rows": 96912,
-
"cost": 10292,
-
"chosen": false
-
},
-
{
-
"rows_to_scan": 193825,
-
"filtering_effect": [
-
],
-
"final_filtering_effect": 0.499997,
-
"access_type": "scan",
-
"resulting_rows": 96912,
-
"cost": 19582.8,
-
"chosen": false
-
}
-
]
-
},
-
"condition_filtering_pct": 100,
-
"rows_for_plan": 96912,
-
"cost_for_plan": 10292,
-
"chosen": true
-
}
-
]
-
},
-
{
-
"attaching_conditions_to_tables": {
-
"original_condition": "(`empm`.`first_name` = 'Aamer')",
-
"attached_conditions_computation": [
-
],
-
"attached_conditions_summary": [
-
{
-
"table": "`empm`",
-
"attached": "(`empm`.`first_name` = 'Aamer')"
-
}
-
]
-
}
-
},
-
{
-
"optimizing_distinct_group_by_order_by": {
-
"simplifying_order_by": {
-
"original_clause": "`empm`.`emp_no` desc",
-
"items": [
-
{
-
"item": "`empm`.`emp_no`"
-
}
-
],
-
"resulting_clause_is_simple": true,
-
"resulting_clause": "`empm`.`emp_no` desc"
-
}
-
}
-
},
-
{
-
"reconsidering_access_paths_for_index_ordering": { --- 使用索引的排序。。。
-
"clause": "ORDER BY",
-
"steps": [
-
],
-
"index_order_summary": {
-
"table": "`empm`",
-
"index_provides_order": false,
-
"order_direction": "undefined", -- 发现这部分是undefined,中间order by部分丢掉了,所以这个索引cost计算丢掉了排序成本
-
"index": "idx_empm",
-
"plan_changed": false
-
}
-
}
-
},
-
{
-
"finalizing_table_conditions": [
-
{
-
"table": "`empm`",
-
"original_table_condition": "(`empm`.`first_name` = 'Aamer')",
-
"final_table_condition ": null
-
}
-
]
-
},
-
{
-
"refine_plan": [
-
{
-
"table": "`empm`"
-
}
-
]
-
},
-
{
-
"considering_tmp_tables": [
-
{
-
"adding_sort_to_table": "empm"
-
}
-
]
-
}
-
]
-
}
-
},
-
{
-
"join_execution": {
-
"select#": 1,
-
"steps": [
-
{
-
"sorting_table": "empm",
-
"filesort_information": [
-
{
-
"direction": "desc",
-
"expression": "`empm`.`emp_no`"
-
}
-
],
-
"filesort_priority_queue_optimization": {
-
"usable": false,
-
"cause": "not applicable (no LIMIT)"
-
},
-
"filesort_execution": [
-
],
-
"filesort_summary": {
-
"memory_available": 262144,
-
"key_size": 8,
-
"row_size": 677,
-
"max_rows_per_buffer": 387,
-
"num_rows_estimate": 18446744073709551615,
-
"num_rows_found": 50106,
-
"num_initial_chunks_spilled_to_disk": 12,
-
"peak_memory_used": 294912,
-
"sort_algorithm": "std::stable_sort",
-
"sort_mode": ""
-
}
-
}
-
]
-
}
-
}
-
]
-
}