MySQL索引优化

2021-03-16  本文已影响0人  传达室马大爷

trace工具

MySQL执行的跟踪工具,通过trace可以查询sql语句具体使用中使用了哪些索引以及索引的性能比较等,方便定位慢查询和优化sql

trace使用
-- 开启trace,json格式输出
set session optimizer_trace="enabled=on",end_markers_in_json=on;
-- 执行慢查询语句
SELECT * from employees where name > 'a';
-- 查询trace结果
SELECT * FROM information_schema.OPTIMIZER_TRACE;
-- 关闭trace
set session optimizer_trace=“enabled=off”;
结果分析
{
    "steps": [
        /* 第一阶段:SQL准备阶段 */
        {
            "join_preparation": {
                "select#": 1,
                "steps": [{
                    "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`employees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from `employees` where (`employees`.`name` > 'a')"
                }] /* steps */
            } /* join_preparation */
        },
        /* 第二阶段:SQL优化阶段 */
        {
            "join_optimization": {
                "select#": 1,
                "steps": [{
                        /* 条件处理 */
                        "condition_processing": {
                            "condition": "WHERE",
                            "original_condition": "(`employees`.`name` > 'a')",
                            "steps": [{
                                    "transformation": "equality_propagation",
                                    "resulting_condition": "(`employees`.`name` > 'a')"
                                },
                                {
                                    "transformation": "constant_propagation",
                                    "resulting_condition": "(`employees`.`name` > 'a')"
                                },
                                {
                                    "transformation": "trivial_condition_removal",
                                    "resulting_condition": "(`employees`.`name` > 'a')"
                                }
                            ] /* steps */
                        } /* condition_processing */
                    },
                    {
                        "substitute_generated_columns": {} /* substitute_generated_columns */
                    },
                    /* 表依赖详情 */
                    {
                        "table_dependencies": [{
                            "table": "`employees`",
                            "row_may_be_null": false,
                            "map_bit": 0,
                            "depends_on_map_bits": [] /* depends_on_map_bits */
                        }] /* table_dependencies */
                    },
                    {
                        "ref_optimizer_key_uses": [] /* ref_optimizer_key_uses */
                    },
                    /* 预估表的访问成本 */
                    {
                        "rows_estimation": [{
                            "table": "`employees`",
                            "range_analysis": {
                                /* 全表扫描 */
                                "table_scan": {
                                    /* 扫描行数 */
                                    "rows": 3, 
                                    /* 查询成本 */
                                    "cost": 3.7 
                                } /* table_scan */ ,
                                /* 查询可能使用的索引 */
                                "potential_range_indexes": [
                                    /* 主键索引 */
                                    {
                                        "index": "PRIMARY",
                                        "usable": false,
                                        "cause": "not_applicable"
                                    },
                                    /* 辅助索引 */
                                    {
                                        "index": "idx_name_age_position",
                                        "usable": true,
                                        "key_parts": [
                                            "name",
                                            "age",
                                            "position",
                                            "id"
                                        ] /* key_parts */
                                    }
                                ] /* potential_range_indexes */ ,
                                "setup_range_conditions": [] /* setup_range_conditions */ ,
                                "group_index_range": {
                                    "chosen": false,
                                    "cause": "not_group_by_or_distinct"
                                } /* group_index_range */ ,
                                /* 分析各个索引使用成本 */
                                "analyzing_range_alternatives": {
                                    "range_scan_alternatives": [{
                                        "index": "idx_name_age_position",
                                        /* 索引使用范围 */
                                        "ranges": [
                                            "a < name"
                                        ] /* ranges */ ,
                                        "index_dives_for_eq_ranges": true,
                                        /* 使用该索引获取的记录是否按照主键排序 */
                                        "rowid_ordered": false,
                                        "using_mrr": false,
                                        /* 是否使用覆盖索引 */
                                        "index_only": false,
                                        /* 索引扫描行数 */
                                        "rows": 3,
                                        /* 索引使用成本 */
                                        "cost": 4.61,
                                        /* 可能选择该索引 */
                                        "chosen": false,
                                        "cause": "cost"
                                    }] /* range_scan_alternatives */ ,
                                    "analyzing_roworder_intersect": {
                                        "usable": false,
                                        "cause": "too_few_roworder_scans"
                                    } /* analyzing_roworder_intersect */
                                } /* analyzing_range_alternatives */
                            } /* range_analysis */
                        }] /* rows_estimation */
                    },
                    {
                        "considered_execution_plans": [{
                            "plan_prefix": [] /* plan_prefix */ ,
                            "table": "`employees`",
                            /* 最优访问路径 */
                            "best_access_path": {
                                /* 决定的访问路径 */
                                "considered_access_paths": [{
                                    "rows_to_scan": 3,
                                    /* 访问类型 scan:全表扫描 */
                                    "access_type": "scan",
                                    "resulting_rows": 3,
                                    "cost": 1.6,
                                    /* 确定选择 */
                                    "chosen": true
                                }] /* considered_access_paths */
                            } /* best_access_path */ ,
                            "condition_filtering_pct": 100,
                            "rows_for_plan": 3,
                            "cost_for_plan": 1.6,
                            "chosen": true
                        }] /* considered_execution_plans */
                    },
                    {
                        "attaching_conditions_to_tables": {
                            "original_condition": "(`employees`.`name` > 'a')",
                            "attached_conditions_computation": [] /* attached_conditions_computation */ ,
                            "attached_conditions_summary": [{
                                "table": "`employees`",
                                "attached": "(`employees`.`name` > 'a')"
                            }] /* attached_conditions_summary */
                        } /* attaching_conditions_to_tables */
                    },
                    {
                        "refine_plan": [{
                            "table": "`employees`"
                        }] /* refine_plan */
                    }
                ] /* steps */
            } /* join_optimization */
        },
        /* 第三阶段:SQL执行阶段 */
        {
            "join_execution": {
                "select#": 1,
                "steps": [] /* steps */
            } /* join_execution */
        }
    ] /* steps */
}

结论:全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描

MySQL排序

Using index

是MySQL扫描索引本身就可以完成排序

Order by使用Using index索引排序需要满足以下两个条件:

Using filesort

Using filesort是文件排序,并不是在磁盘中进行排序,而是告诉我们是一种排序算法,文件排序通过相应排序算法将数据读取到内存中,MySQL在内存中对数据进行排序。通过sort_buffer_size字段排序区大小,排序区线程独占,同一时间MySQL中有多个sort_buffer_size排序区同时存在

MySQL 通过比较系统变量 max_length_for_sort_data(默认1024字节) 的大小和需要查询的字段总大小来判断使用哪种排序模式。

单路排序执行流程

双路排序执行流程

表关联查询

驱动表:表关联查询数据量少的表为驱动表

被驱动表:表关联查询数据量大的表为被驱动表

MySQL在关联字段有索引时使用NLJ算法,没有索引时使用BNL算法,有索引的情况下NLJ算法比BNL算法效率更高

嵌套循环连接 Nested-Loop Join(NLJ) 算法

一行一行的从循环从驱动表中读取行数据,在这行数据中取关联字段,根据关联字段去被驱动表中查询满足条件的行,然后取出两张表的合集,小表驱动大表

-- t1为大表,t2为小表
-- t1表上a字段创建idx_a索引
EXPLAIN select*from t1 inner join t2 on t1.a= t2.a;
image-20210315233330104
    由执行计划可知,t2为驱动表,t1为被驱动表,优化器一般会选择小表作为驱动表。索引 inner join时排在前面的表不一样是驱动表

    如果Extra中未出现Using join buffer,则表示join算法为NLJ算法

sql执行流程

  1. 读取t2表的第一行数据
  2. 从第一步中取出关联字段a,到t1表中查找
  3. 取出t1表满足条件的行,和t2表合并
  4. 重复1-3步骤,直到t2表取完
  5. 合并后返回客户端
基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法

把驱动表的所有数据一次性读取到join_buffer中,然后在扫描被驱动表,把被驱动表每一行数据取出来和join_buffer中的数据比较

-- t1为大表,t2为小表
-- t1的b字段没有创建索引
EXPLAIN select * from t1 inner join t2 on t1.b= t2.b;
image-20210315234226821
    Extra 中 的Using join buffer (Block Nested Loop)说明该关联查询使用的是 BNL 算法。

sql执行流程

  1. 将t2中所有数据存入join buffer中
  2. 取出t1表的第一行数据,与join buffer中的数据做对比
  3. 重复1-2操作,知道t1表全部取完
  4. 返回客户端

in和exists优化

原则:小表驱大表,即小的数据集驱动大的数据集

in

当IN中子查询(t2表)的数据集小于t1表时,in优于exists

SELECT * FROM t1 where id IN (SELECT userId FROM t2)
-- 等价于
for (userId in t2) {
    SELECT * FROM t1 where t1.id = t2.userId
}
exists

当EXISTS中子查询(t2表)的数据集大于t1表时,exists优于in

-- 将主表(t1)的数据放到子查询(t2表)中进行条件校验,根据校验结果(true或false)来决定主表数据是否保留
SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t1.id = t2.userId)

-- 等价于
for (t1.id in t1) {
    SELECT * FROM t2 where t2.userId = t1.id
}
上一篇下一篇

猜你喜欢

热点阅读