关于sql优化:故障分析-MySQL-相同-SQL-不同环境执行时间不一样案例分析

作者:付祥 现居珠海,次要负责 Oracle、MySQL、mongoDB 和 Redis 保护工作。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 1、问题景象开发反馈同一条SQL在qa环境执行须要0.1s,而在dev环境须要0.3~0.5s,SQL如下: SELECT machine.id, machine.asset_number, machine.sn, machine.state, machine.idc_id, machine.cabinet_id, machine.cabinet_order, machine.unit_size, machine.brand_model, machine.buy_time, machine.expiration_time, machine.warranty, machine.renewstart_time, machine.renewend_time, machine.warranty_company_id, machine.renewal_type, machine.check_hardware, machine.machine_purchase_price, machine.tags, machine.memo, machine.cpu_core_count, machine.cpu_model, machine.cpu_count, machine.memory_count, machine.memory_size, machine.wire_standard, machine.disk_num, machine.netcard_total_count, machine.netcard_1g, machine.netcard_10g, machine.os_version, machine.kernel_version, machine.raid, machine.power, machine.firmware, machine.manage_card_ip, machine.hostname, machine.private_mac, machine.public_mac, machine.private_ip, machine.public_ip, machine.other_ips, machine.create_time, machine.update_time, machine.creator, machine.updater, machine.delete_flag, machine.disk_desc_id, res.id res_id, res.owner_company_code, res.owner_company_name, res.project_id, res.project_group_id, res.sub_project_id, res.finance_product_id, res.finance_product_name, res.sub_project_name, res.admin_id, res.admin_name, res.owner_id, res.owner_name, 2 AS resource_type, res.resource_id, res.machine_usage_types, res.machine_usage_names, cdl1.display AS check_hardware_name, cdl2.display AS state_name, cdl3.display AS brand_model_name, cdl4.display AS renewal_type_name, cdl5.display AS power_name, cdl6.display AS unit_size_name, cec.company_name AS warranty_company_name, cc.serial_number AS cabinet_name, ci.name AS idc_name, dd.disk_desc AS disk_desc_name, machine.virtual_ip, machine.qingteng_binded, machine.qingteng_id, machine.remark FROM CMDB_PHYSICAL_MACHINE machine LEFT JOIN cmdb_dropdown_list cdl1 ON (machine.check_hardware=cdl1.code and cdl1.type="HardwareCheck") LEFT JOIN cmdb_dropdown_list cdl2 ON (machine.state=cdl2.code and cdl2.type="DeviceStatus") LEFT JOIN cmdb_dropdown_list cdl3 ON (machine.brand_model=cdl3.code and cdl3.type="BrandModels") LEFT JOIN cmdb_dropdown_list cdl4 ON (machine.renewal_type=cdl4.code and cdl4.type="RenewalType") LEFT JOIN cmdb_dropdown_list cdl5 ON (machine.power=cdl5.code and cdl5.type="PowerInfo") LEFT JOIN cmdb_dropdown_list cdl6 ON (machine.unit_size=cdl6.code and cdl6.type="UnitSize") LEFT JOIN cmdb_external_company cec ON (machine.warranty_company_id=cec.id) LEFT JOIN cmdb_cabinet cc ON (machine.cabinet_id=cc.id) LEFT JOIN cmdb_disk_desc dd ON (machine.disk_desc_id=dd.id) inner JOIN cmdb_idc ci ON (machine.idc_id=ci.id and ci.delete_flag=0) left join cmdb_resource_group res on (machine.id = res.resource_id and res.resource_type = 2) where 1=1 AND machine.delete_flag=0 order by id desc LIMIT 0,302、剖析查看SQL执行打算,发现2个环境执行打算不一样,导致执行效率不同。 ...

February 13, 2023 · 10 min · jiezi

关于sql优化:第39期MySQL-时间类分区写-SQL-注意事项

上篇《MySQL 工夫类分区具体实现》介绍了工夫类分区的实现办法,本篇是对上篇的一个延长,介绍基于此类分区的相干 SQL 编写注意事项。对于分区表的检索无非有两种,一种是带分区键,另一种则不带分区键。一般来讲检索条件带分区键则执行速度快,不带分区键则执行速度变慢。这种论断适应于大多数场景,但不能以偏概全,要针对不同的分区表定义来写最合适的 SQL 语句。用分区表的目标是为了缩小 SQL 语句检索时的记录数,如果没有达到预期成果,则分区表只能带来副作用。 接下来我列举几个经典的 SQL 语句: 仔细的读者在浏览完上篇可能心中就有一些疑难,基于表 ytt_p1 的 SQL 语句如下: select count(*) from ytt_pt1 where log_date >='2018-01-01' and log_date <'2019-01-01';同样是分区表 ytt_pt1_month1 ,基于这张表的 SQL 语句如下: select count(*) from ytt_pt1_month1 where log_date in ('2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05','2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10','2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15');两张表的检索需要相似,为何写法差别不小? 后者为何要写成列表模式而不持续写成简略的范畴检索模式?带着这点疑难,咱们持续。 MySQL 针对分区表有一项优化技术叫 partition pruning ,翻译过去就是分区裁剪。其大抵含意是 MySQL 会依据 SQL 语句的过滤条件对应的分区函数进行计算,并把计算结果穿透到底层分区表从而减小扫描记录数的一种优化策略。对于工夫类型(DATE,TIMESTAMP,TIME,DATETIME),MySQL 仅反对局部函数的分区裁剪:to_days,to_seconds,year,unix_timestamp。那么咱们再来看之前的疑难:表 ytt_pt1_month1 分区函数为 month ,MySQL 分区表尽管反对 month 函数,然而分区裁剪技术却不蕴含这个函数。 接下来,分两局部来介绍本篇内容。 第一、来体验下 MySQL 的分区裁剪技术,新建一张表 pt_pruning:分区函数为 to_days 。create table pt_pruning (id int,r1 int,r2 int,log_date date)partition by range(to_days(log_date))(PARTITION p_01 VALUES LESS THAN (to_days('2020-02-01')) ENGINE = InnoDB, PARTITION p_02 VALUES LESS THAN (to_days('2020-03-01')) ENGINE = InnoDB, PARTITION p_03 VALUES LESS THAN (to_days('2020-04-01')) ENGINE = InnoDB, PARTITION p_04 VALUES LESS THAN (to_days('2020-05-01')) ENGINE = InnoDB, PARTITION p_05 VALUES LESS THAN (to_days('2020-06-01')) ENGINE = InnoDB, PARTITION p_06 VALUES LESS THAN (to_days('2020-07-01')) ENGINE = InnoDB, PARTITION p_07 VALUES LESS THAN (to_days('2020-08-01')) ENGINE = InnoDB, PARTITION p_08 VALUES LESS THAN (to_days('2020-09-01')) ENGINE = InnoDB, PARTITION p_09 VALUES LESS THAN (to_days('2020-10-01')) ENGINE = InnoDB, PARTITION p_10 VALUES LESS THAN (to_days('2020-11-01')) ENGINE = InnoDB, PARTITION p_11 VALUES LESS THAN (to_days('2020-12-01')) ENGINE = InnoDB, PARTITION p_12 VALUES LESS THAN (to_days('2021-01-01')) ENGINE = InnoDB, PARTITION p_max VALUES LESS THAN MAXVALUE ENGINE = InnoDB)此表蕴含2020年一整年的数据,大略100W条,此处省略造数据过程。(localhost:ytt)<mysql>select min(log_date),max(log_date),count(*) from pt_pruning;+---------------+---------------+----------+| min(log_date) | max(log_date) | count(*) |+---------------+---------------+----------+| 2020-01-02 | 2020-12-31 | 1000000 |+---------------+---------------+----------+1 row in set (0.72 sec)别离执行上面几条 SQL :SQL 1:求日期蕴含 '2020-01-02' 的记录条数。 ...

February 21, 2022 · 3 min · jiezi

关于sql优化:技术分享-实时慢查询监控系统构建

作者:王春涛 目前是多点Dmall数据库架构师,更早是聚美数据库团队负责人,善于高并发下数据库架构,运维保障,数据库平台建设。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 慢查问监控是MySQL运维中十分重要的一项,它能够帮忙分析线上数据库性能的抖动或者业务查问响应慢等状况。当集群和实例十分多的状况下,慢查问的收集和存储会变得比拟艰难,而且不太好做到实时的慢查问告警。 罕用计划介绍1、慢日志收集通常状况下会采纳通过定时工作的形式应用 pt-query-digest 将每个实例的慢日志收集写入到 MySQL 数据库。因为是定时工作触发,所以并不是实时的进行收集上报。 2、慢日志统计通过查问 MySQL 数据库能够依据 host 、port 、user 、指纹、工夫范畴等条件进行查问统计 3、慢日志告警从 MySQL 中查问出慢日志而后匹配到对应的 DBA 和研发人员发送告警。但因为 MySQL 中数据是全量存在的只能依据工夫范畴进行批次查问,告警就无奈做到实时。 pt-query-digest 的办法在采集的时候就曾经不是实时了,再加上告警工作是按工夫范畴进行批次查问所以这套架构下的慢查问监控不能做到实时的监控 上面给大家介绍一下多点数据库实时慢查问监控的实现思路。多点实时慢查问监控整体架构 如上图,咱们有一个监听 slowlog 的 agent ,这个 agent 次要是继续的对慢查问 log 文件进行 tail ,将每一个 slowlog 段作为一个 list 的 iterm push 到 redis 。每个 agent 能够监听所在机器的所有 MySQL 实例的慢日志,这样就把扩散在各个机器上的日志会集到了一个 redis 中。而后有一个生产端也就是 slowlog 推送服务,从 redis 中 pop 出组装好的慢日志,依据 pop 出的慢日志解析其中的 host ,port ,dbname 以及 user ,匹配到对应的 dba 或研发,将慢日志实时推送给对应的人员。同时依照 host-port 将慢查问存储为文件。这样就造成了一个流式的解决,再加上 redis 的全内存操作,速度极快,齐全能够做到实时。 ...

October 22, 2021 · 1 min · jiezi

关于sql优化:第23期索引设计组合索引不适用场景改造

上篇文章曾经具体介绍 MySQL 组合索引的概念以及其实用场景,这篇次要介绍 MySQL 组合索引的不实用场景以及革新计划。 回顾下组合索引的语法和必备条件【组合索引的语法】(只探讨默认升序排列) alter table t1 add idx_multi(f1, f2, f3);【必备条件】列 f1 必须存在于 SQL 语句过滤条件中! 也就是说组合索引的第一个列(最左列)在过滤条件中必须存在,而且最好是等值过滤。看看上面这些 SQL,没有一款适宜组合索引。 # SQL 1select * from t1 where f2 = 1;# SQL 2select * from t1 where f3 = 1;# SQL 3select * from t1 where f2 = 1 and f3 = 1;# SQL 4select * from t1 where f2 = 1 or f3 = 1;# SQL 5select * from t1 where f1 = 1 or f2 = 1;# SQL 6select * from t1 where f1 = 1 or f3 = 1;# SQL 7select * from t1 where f1 = 1 or f2 = 1 or f3 = 1;那接下来对下面的 SQL 一个一个剖析: ...

March 10, 2021 · 4 min · jiezi

关于sql优化:技术分享-explain-formatjson-详解

作者:胡呈清爱可生 DBA 团队成员,善于故障剖析、性能优化,集体博客:https://www.jianshu.com/u/a95...,欢送探讨。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 explain format=json 能够打印具体的执行计划成本,上面两个示例将通知你如何查看老本输入,以及如何计算成本。 表构造如下: mysql> show create table sbtest1\G*************************** 1. row *************************** Table: sbtest1Create Table: CREATE TABLE `sbtest1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '', `pad` varchar(90) COLLATE utf8mb4_bin DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4316190 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin##留神sbtest3无主键mysql> show create table sbtest3\G*************************** 1. row *************************** Table: sbtest3Create Table: CREATE TABLE `sbtest3` ( `id` int(11) NOT NULL, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) COLLATE utf8mb4_bin NOT NULL DEFAULT '', `pad` varchar(66) COLLATE utf8mb4_bin DEFAULT NULL, KEY `k_3` (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin示例 1mysql> explain format=json select * from sbtest3 where id<100 and k<200\G*************************** 1. row ***************************EXPLAIN: { "query_block": { "select_id": 1, "cost_info": { "query_cost": "26.21" ##查问总成本 }, "table": { "table_name": "sbtest3", ##表名 "access_type": "range", ##拜访数据的形式是range,即索引范畴查找 "possible_keys": [ "k_3" ], "key": "k_3", ##应用索引 "used_key_parts": [ "k" ], "key_length": "4", "rows_examined_per_scan": 18, ##扫描 k_3 索引的行数:18(满足特定条件时应用index dive可失去真履行数) "rows_produced_per_join": 5, ##在扫描索引后估算满足id<100条件的行数:5 "filtered": "33.33", ##在扫描索引后估算满足其余条件id<100的数据行占比 "index_condition": "(`sbtest`.`sbtest3`.`k` < 200)", ##索引条件 "cost_info": { "read_cost": "25.01", ##这里蕴含了所有的IO老本+局部CPU老本 "eval_cost": "1.20", ##计算扇出的CPU老本 "prefix_cost": "26.21", ##read_cost+eval_cost "data_read_per_join": "4K" }, "used_columns": [ "id", "k", "c", "pad" ], "attached_condition": "(`sbtest`.`sbtest3`.`id` < 100)" } }}eval_cost这个很简略,就是计算扇出的 CPU 老本。利用条件 k<200 时,须要扫描索引 18行,这里 18 是准确值(index dive),而后优化器用了一种叫启发式规定(heuristic)的算法估算出其中满足条件 id<100 的比例为 33.33%,进行 18*33.33% 次计算的 CPU 老本等于 18*33.33%*0.2=1.2,这里 0.2 是老本常数(即 row_evaluate_cost )。 ...

March 1, 2021 · 3 min · jiezi

关于sql优化:第22期索引设计组合索引适用场景

建设在多个列上的索引即组合索引(联结索引),实用在多个列必须一起应用或者是从左到右方向局部间断列一起应用的业务场景。 组合索引和单值索引相似,索引上的每个键值依照肯定的大小排序。比方针对三个字段的组合索引有以下组合: (f1, f2, f3)(f1, f2, f3 desc)(f1, f2 desc, f3)(f1 desc, f2, f3)(f1 desc, f2 desc, f3 desc)...明天探讨的组合索引只基于默认排序形式,也就是 (f1,f2,f3),等价于 (f1 asc, f2 asc, f3 asc)。 组合索引的语法: alter table t1 add key idx_multi(f1 [asc/desc],f2 [asc/desc],f3 [asc/desc]) [using btree/using hash]MySQL 里,组合索引最大反对 16 个列。能够基于 B+ 树,也能够基于哈希,这篇次要探讨基于 B 树,并且索引程序默认升序,基于 HASH 只有一种用法,就是所有列的都必须等值过滤【仅限于上面 SQL 3】。 应用组合索引的必备条件为:列 f1 必须存在于 SQL 语句过滤条件中!也就是说组合索引的第一个列(最左列)在过滤条件中必须存在,而且最好是等值过滤。 思考以下 15 条 SQL 语句, 别离对表 t1 字段 f1、f2、f3 有不同的组合过滤,并且都蕴含了列 f1,也就是说满足了组合索引应用的必备条件。 # SQL 1select * from t1 where f1 = 1;# SQL 2select * from t1 where f1 = 1 and f2 = 1;# SQL 3select * from t1 where f1 = 1 and f2 = 1 and f3 = 1 ;# SQL 4select f1,f2 from t1 where 1 order by f1,f2;# SQL 5select f1,f2,f3 from t1 where 1 order by f1,f2,f3;# SQL 6select f1,f2,count(*) from t1 group by f1,f2;# SQL 7select f1,f2,f3,count(*) from t1 group by f1,f2,f3;# SQL 8select * from t1 where f1 = 10 and f2 = 5 and f3 > 10# SQL 9select * from t1 where f1 = 10 and f2 > 5;# SQL 10select * from t1 where f1 < 10;# SQL 11select * from t1 where f1 < 10 and f2 > 5;# SQL 12select * from t1 where f1 < 10 and f2 > 5 and f3 < 10;# SQL 13select * from t1 where f1 < 10 and f2 = 5 and f3 < 10;# SQL 14select * from t1 where f1 < 10 and f2 = 5 and f3 = 10;# SQL 15select * from t1 where f1 = 1 and f3 = 1;SQL 1、SQL 2、 SQL 3 三条 SQL 别离基于组合索引 idx_multi 过滤后回表;其中 SQL 3 是组合索引中每个字段都能过滤到的最完满查问。来看看 SQL 3的执行打算: ...

February 24, 2021 · 6 min · jiezi

关于sql优化:一次对group-by时间导致的慢查询的优化

前言:最近在测试环境中点击一个图表展现页面时,半天才失去后盾响应的数据进行页面渲染展现,后盾的响应很慢,这样极大的升高了用户的体验;发现这个问题后马上进行了排查 ,通过排查发现是由一个查问很慢的 group by 语句导致的; 本文主线: ①、简略形容下排查步骤; ②、对 group by 查问慢进行优化; 简略形容下排查步骤:排查次要分为了两个步骤: 后盾接口的监控,看看哪个办法调用时耗时多数据库开启慢查问日志,记录执行很慢的SQL举荐应用阿里开源的Java线上诊断工具 Arthas ,应用其 trace 命令统计办法调用链路上各个办法节点的耗时; Arthas 工具的具体应用办法可参考: 线上服务响应工夫太长的排查心路 ; 通过应用Arthas工具统计到一个进行数据库的 group by查问 办法耗时很重大; 为了进一步确定是这个查问SQL 很耗时,将MySql 的慢查问日志开启了,而后再次调用后盾这个接口,发现慢查问日志中的确存在了这个SQL语句; SQL语句如下: SELECT date_format(createts, '%Y') AS YEARFROM t_test_logGROUP BY date_format(createts, '%Y')ORDER BY createts DESC这个SQL语句是用来统计表中所有数据被创立时的年份;上面就来聊聊这个SQL为什么会比较慢,而后进行了怎么的优化; 对 group by 查问慢进行优化:在优化group by查问的时候,个别会想到上面这两个名词,通过上面这两种索引扫描能够高效疾速的实现group by操作: 涣散索引扫描(Loose Index Scan)紧凑索引扫描(Tight Index Scan)group by操作在没有适合的索引可用时,通常先扫描整个表提取数据并创立一个长期表,而后依照group by指定的列进行排序;在这个长期表外面,对于每一个group 分组的数据行来说是间断在一起的。 实现排序之后,就能够失去所有的groups 分组,并能够执行汇集函数(aggregate function)。 能够看到,在没有应用索引的时候,须要创立长期表和排序;那在执行打算的 Extra 额定信息中通常就会看到这些信息 Using temporary; Using filesort 呈现 。 ...

January 4, 2021 · 2 min · jiezi

关于sql优化:数据量很大分页查询很慢有什么优化方案

小Hub领读:当页数比拟大的时候,查问效率直线降落,有什么方法能够优化吗?看完这篇文章! 作者:悠悠i起源:cnblogs.com/youyoui/p/7851007.html 筹备工作个别分页查问应用子查问优化应用 id 限定优化应用长期表优化对于数据表的 id 阐明*当须要从数据库查问的表有上万条记录的时候,一次性查问所有后果会变得很慢,特地是随着数据量的减少特地显著,这时须要应用分页查问。对于数据库分页查问,也有很多种办法和优化的点。上面简略说一下我晓得的一些办法。 筹备工作为了对上面列举的一些优化进行测试,上面针对已有的一张表进行阐明。 表名:order_history形容:某个业务的订单历史表次要字段:unsigned int id,tinyint(4) int type字段状况:该表一共 37 个字段,不蕴含 text 等大型数据,最大为 varchar(500),id 字段为索引,且为递增。数据量:5709294MySQL 版本:5.7.16 线下找一张百万级的测试表可不容易,如果须要本人测试的话,能够写 shell 脚本什么的插入数据进行测试。以下的 sql 所有语句执行的环境没有产生扭转,上面是根本测试后果:select count(*) from orders_history;返回后果:5709294 三次查问工夫别离为: 8903 ms8323 ms8401 ms个别分页查问个别的分页查问应用简略的 limit 子句就能够实现。limit 子句申明如下: SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offsetLIMIT 子句能够被用于指定 SELECT 语句返回的记录数。需注意以下几点: 第一个参数指定第一个返回记录行的偏移量,留神从0开始第二个参数指定返回记录行的最大数目如果只给定一个参数:它示意返回最大的记录行数目第二个参数为 -1 示意检索从某一个偏移量到记录集的完结所有的记录行初始记录行的偏移量是 0(而不是 1)上面是一个利用实例: select * from orders_history where type=8 limit 1000,10;该条语句将会从表 orders_history 中查问offset: 1000开始之后的 10 条数据,也就是第 1001 条到第 1010 条数据(1001 <= id <= 1010)。 数据表中的记录默认应用主键(个别为 id)排序,下面的后果相当于: select * from orders_history where type=8 order by id limit 10000,10;三次查问工夫别离为: 3040 ms3063 ms3018 ms针对这种查问形式,上面测试查问记录量对工夫的影响: select * from orders_history where type=8 limit 10000,1;select * from orders_history where type=8 limit 10000,10;select * from orders_history where type=8 limit 10000,100;select * from orders_history where type=8 limit 10000,1000;select * from orders_history where type=8 limit 10000,10000;三次查问工夫如下: 查问 1 条记录:3072ms 3092ms 3002ms查问 10 条记录:3081ms 3077ms 3032ms查问 100 条记录:3118ms 3200ms 3128ms查问 1000 条记录:3412ms 3468ms 3394ms查问 10000 条记录:3749ms 3802ms 3696ms另外我还做了十来次查问,从查问工夫来看,根本能够确定,在查问记录量低于 100 时,查问工夫根本没有差距,随着查问记录量越来越大,所破费的工夫也会越来越多。 ...

August 11, 2020 · 2 min · jiezi

你真的了解SQL吗SQL优化最佳实践作者带你重新了解SQL

一、SQL :一种熟悉又陌生的编程语言这里有几个关键词;“熟悉”、“陌生”、“编程语言”。 说它“熟悉”,是因为它是DBA和广大开发人员,操作数据库的主要手段,几乎每天都在使用。说它“陌生”,是很多人只是简单的使用它,至于它是怎么工作的?如何才能让它更高效的工作?却从来没有考虑过。 这里把SQL归结为一种“编程语言”,可能跟很多人对它的认知不同。让我们看看它的简单定义(以下内容摘自百度百科) 结构化查询语言(Structured Query Language),简称SQL,是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。结构化查询语言是高级的非过程化编程语言,允许用户在高层数据结构上工作。它不要求用户指定对数据的存放方法,也不需要用户了解具体的数据存放方式,所以具有完全不同底层结构的不同数据库系统, 可以使用相同的结构化查询语言作为数据输入与管理的接口。结构化查询语言语句可以嵌套,这使它具有极大的灵活性和强大的功能。总结一句话,SQL是一种非过程化的的编程语言,可通过它去访问关系型数据库系统。 二、你真的了解“SQL”吗?下面我会通过一个小例子,看看大家是否真正了解SQL。 这是一个很简单的示例,是关于SQL语句执行顺序的。这里将一个普通的SELECT语句,拆分为三个子句。那么在实际的执行过程中,是按照什么顺序处理的呢?这里有A-F六个选项,大家可以思考选择一下… 最终的答案是D,即按照先执行FROM子句,然后WHERE子句,最后是SELECT部分。 针对上面的示例,让我们真实构造一个场景,通过查看执行计划看看是否按照我们选择的顺序执行的。关于执行计划的判读,我后面会专门谈到。这里我先解释一下整个执行过程。 第一步,是按照全表扫描的方式访问了对象表(EMP)。对应于语句中的FROM部分。第二步,是对提取出的结果集进行了过滤(filter部分),即将满足条件的记录筛选出来。对应于语句中的WHERE部分。第三步,是对满足条件的记录进行字段投射,即将需要显示的字段提取出来。对应于语句中的SELECT部分。 这是一个详细的SQL各部分执行顺序的说明。 通过对执行顺序的理解,可以为我们未来的优化工作带来很大帮助。一个很浅显的认识就是,优化动作越靠前越好。 三、SQL现在是否仍然重要?这里引入了一个新的问题,在现有阶段SQL语言是否还重要? 之所以引入这一话题,是因为随着NOSQL、NEWSQL、BIGDATA等技术逐步成熟推广,“SQL语言在现阶段已经变得不那么重要”成为一些人的观点。那实际情况又是如何呢? 让我们先来看一张经典的图。图中描述了传统SMP架构的关系型数据库、MPP架构的NEWSQL、MPP架构的NoSQL不同方案的适用场景对比。 从上面的“数据价值密度、实时性”来看,传统关系型数据库适合于价值密度更高、实时性要求更高的场景(这也就不难理解类似账户、金额类信息都是保存在传统关系型数据库中);MPP架构的NewSQL次之,MPP架构的NoSQL更适合于低价值、实时性要求不高的场景。 从下面的“数据规模”来看,传统关系型数据库适合保存的大小限制在TB级别,而后两者可在更大尺度上(PB、EB)级保存数据。 从下面的“典型场景”来看,传统关系型数据库适合于OLTP在线交易系统;MPP架构的NewSQL适合于OLAP在线分析系统;而NoSQL的使用场景较多(利于KV型需求、数据挖掘等均可以考虑)。 最后从“数据特征”来看,前两者适合于保存结构化数据,后者更适合于半结构化、乃至非结构化数据的保存。 归纳一下,不同技术有其各自特点,不存在谁代替谁的问题。传统关系型数据库有其自身鲜明特点,在某些场合依然是不二选择。而作为其主要交互语言,SQL必然长期存在发展下去。 我们再来对比一下传统数据库与大数据技术。从数据量、增长型、多样化、价值等维度对比两种技术,各自有其适用场景。 对于大数据领域而言,各种技术层出不穷。但对于广大使用者来说,往往会存在一定的使用门槛,因此现在的一种趋势就是在大数据领域也引入“类SQL”,以类似SQL的方式访问数据。这对于广大使用者来说,无疑大大降低了使用门槛。 解答一些疑问: NoSQL、NewSQL已经超越了传统数据库,SQL没有了用武之地! 各种技术有着各自适合的不同场景,不能一概而论。SQL语言作为关系型数据库的主要访问方式,依然有其用武之地。 以后都是云时代了,谁还用关系型数据库! 对于价值密度高,严格一致性的场景,仍然适合采用关系型数据库作为解决方案。 我编程都是用OR Mapping工具,从不需要写SQL! 的确,引入OR Mapping工具大大提高了生产效率,但是它的副作用也很明显,那就是对语句的运行效率失去了控制。很多低效的语句,往往是通过工具直接生成的。这也是为什么有的Mapping工具还提供了原始的SQL接口,用来保证关键语句的执行效率。 大数据时代,我们都用Hadoop、Spark了,不用写SQL啦! 无论是使用Hadoop、Spark都是可以通过编写程序完成数据分析的,但其生产效率往往很低。这也是为什么产生了Hive 、Spark SQL等“类SQL”的解决方案来提高生产效率。 数据库处理能力很强,不用太在意SQL性能! 的确,随着多核CPU、大内存、闪存等硬件技术的发展,数据库的处理能力较以前有了很大的增强。但是SQL的性能依然很重要。后面我们可以看到,一个简单SQL语句就可以轻易地搞垮一个数据库。 SQL优化,找DBA就行了,我就不用学了! SQL优化是DBA的职责范畴,但对于开发人员来讲,更应该对自己的代码负责。如果能在开发阶段就注重SQL质量,会避免很多低级问题。 我只是个运维DBA,SQL优化我不行! DBA的发展可分为“运维DBA->开发DBA->数据架构师…”。如果只能完成数据库的运维类工作,无疑是技能的欠缺,也是对各人未来发展不利。况且,随着Paas云的逐步推广,对于数据库的运维需求越来越少,对于优化、设计、架构的要求越来越多。因此,SQL优化是每个DBA必须掌握的技能。 现在优化有工具了,很简单的! 的确现在有些工具可以为我们减少些优化分析工作,会自动给出一些优化建议。但是,作为DBA来讲,不仅要知其然,还要知其所以然。况且,数据库优化器本身就是一个非常复杂的组件,很难做到完全无误的优化,这就需要人工的介入,分析。 优化不就是加索引嘛,这有啥! 的确,加索引是一个非常常用的优化手段,但其不是唯一的。且很多情况下,加了索引可能导致性能更差。后面,会有一个案例说明。 四、SQL仍然很重要! 我们通过一个示例,说明一下理解SQL运行原理仍然很重要。 这是我在生产环境碰到的一个真实案例。Oracle数据库环境,两个表做关联。执行计划触目惊心,优化器评估返回的数据量为3505T条记录,计划返回量127P字节,总成本9890G,返回时间999:59:59。 从执行计划中可见,两表关联使用了笛卡尔积的关联方式。我们知道笛卡尔连接是指在两表连接没有任何连接条件的情况。一般情况下应尽量避免笛卡尔积,除非某些特殊场合。否则再强大的数据库,也无法处理。这是一个典型的多表关联缺乏连接条件,导致笛卡尔积,引发性能问题的案例。 从案例本身来讲,并没有什么特别之处,不过是开发人员疏忽,导致了一条质量很差的SQL。但从更深层次来讲,这个案例可以给我们带来如下启示: 开发人员的一个疏忽,造成了严重的后果,原来数据库竟是如此的脆弱。需要对数据库保持一种"敬畏"之心。电脑不是人脑,它不知道你的需求是什么,只能用写好的逻辑进行处理。不要去责怪开发人员,谁都会犯错误,关键是如何从制度上保证不再发生类似的问题。五、SQL优化法则下面我们来看看常见的优化法则。这里所说的优化法则,其实是指可以从那些角度去考虑SQL优化的问题。可以有很多种方式去看待它。下面列举一二。 这里来自阿里-叶正盛的一篇博客里的一张图,相信很多人都看过。这里提出了经典的漏斗优化法则,高度是指我们投入的资源,宽度是指可能实现的收益。从图中可见,“减少数据访问”是投入资源最少,而收益较多的方式;“增加硬件资源”是相对投入资源最多,而收益较少的一种方式。受时间所限,这里不展开说明了。 这是我总结的一个优化法则,简称为“DoDo”法则。 第一条,“Do Less or not do!”翻译过来,就是尽量让数据库少做工作、甚至不做工作。 ...

July 11, 2019 · 1 min · jiezi

MySQL分页优化实验与总结

前言分页的sql优化是日常开发中经常遇到的问题,笔者在此做一个经验总结,并附上相应的实验过程。 实验准备若不想亲自实验的,可以直接跳过这一节。但还是建议大家做一下实验,眼见为实。 1.安装测试数据库本次实验使用的数据是mysql官方提供的employee数据库,mysql官方提供了一些测试数据库,可以在这里找到https://dev.mysql.com/doc/ind...。 2.修改测试数据库安装好employee数据库后,笔者出于测试修改了一下salaries表的结构,方便测试,修改操作如下: //修改原表的主键为idCREATE TABLE `test_salaries` ( `id` int(11) NOT NULL AUTO_INCREMENT, `emp_no` int(11) NOT NULL, `salary` int(11) NOT NULL, `from_date` date NOT NULL, `to_date` date NOT NULL, PRIMARY KEY (`id`), CONSTRAINT `test_salaries_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=latin1; //导入原表数据INSERT INTO test_salaries (id,emp_no,salary,from_date,to_date) SELECT NULL,emp_no,salary,from_date,to_date FROM salaries;3.完成测试环境至此,实验的准备工作完成。可先查看一下test_salaries表中有多少数据(以下测试基于该表) SELECT count(*)FROM test_salaries; 优化分页SQL查询优化分页SQL查询的思路: 尽可能使用索引覆盖扫描,而不是查询所有的列。然后根据需要做一次关联操作再返回所需的列(延迟关联)将limit查询转换为已知位置的查询,让mysql通过范围扫描获得对应的结果(范围扫描)延迟关联原始sql查询语句: SELECT * FROM test_salaries WHERE salary <= 94000 LIMIT 2677500,10;原始sql查询语句执行效果: ...

June 3, 2019 · 1 min · jiezi

了解MySQL中EXPLAIN解释命令

1 EXPLAIN概念EXPLAIN会向我们提供一些MySQL是执行sql的信息:EXPLAIN可以解释说明 SELECT, DELETE, INSERT, REPLACE, and UPDATE 等语句.当EXPLAIN与可解释的语句一起使用时,mysql会显示一些来自于优化器的关于sql执行计划的信息。即mysql解释它是如何处理这些语句的,和表之间是如何连接的。想获取更多关于EXPLAIN如何获取执行计划信息的。当EXPLAIN后面是一个会话的connection_id 而不是一个可执行的语句时,它会展示会话的信息。对于SELECT语句,EXPLAIN会产生额外的执行计划信息,这些信息可以用SHOW WARNINGS显示出来。EXPLAIN对于检查设计分区表的查询时非常有用。FORMAT选项可以用于选择输出格式,如果没有配置FORMAT选项,默认已表格形式输出。JSON 选项让信息已json格式展示。2 EXPLAIN 输出列信息EXPLAIN输出的字段信息第一列:列名, 第二列:FORMAT = JSON时输出中显示的等效属性名称 ,第三列:字段含义ColumnJSON NameMeaningidselect_idselect标识号select_typeNoneselect类型tabletable_name这一行数据是关于哪张表的partitionspartitions匹配的分区,对于未分区表,该值为空typeaccess_type使用的连接类别,有无使用索引possible_keyspossible_keysMySQL能使用哪个索引在该表中找到行keykeyMySQL实际决定使用的键(索引)key_lenkey_lengthMySQL决定使用的键长度。如果键是NULL,长度为NULLrefref与索引关联的列rowsrowsmysql认为执行sql时必须被校验的行数filteredfiltered表示此查询条件所过滤的数据的百分比ExtraNone附加信息2.1 idSELECT标识符。SELECT在查询中的序列号,可以为空。2.2 select_typeSELECT类型,所有类型在下表中展示,JSON格式的EXPLAIN将SELECT类型公开为query_block的属性,除非它是SIMPLE或PRIMARY。 JSON名称(不适用为None)也显示在表中。select_type ValueJSON NameMeaningSIMPLENone简单SELECT(不使用UNION或子查询等)PRIMARYNone嵌套查询时最外层的查询UNIONNoneUNION中的第二个或后面的SELECT语句DEPENDENT UNIONdependent (true)UNION中的第二个或以后的SELECT语句,取决于外部查询UNION RESULTunion_resultUNION的结果SUBQUERYNone子查询中的第一个选择DEPENDENT SUBQUERYdependent (true)子查询中的第一个选择,取决于外部查询DERIVEDNone派生表(子查询中产生的临时表)MATERIALIZEDmaterialized_from_subquery物化子查询UNCACHEABLE SUBQUERYcacheable (false)无法缓存结果的子查询,必须对外部查询的每一行进行重新计算UNCACHEABLE UNIONcacheable (false)UNION中属于不可缓存子查询的第二个或以后的选择(请参 UNCACHEABLE SUBQUERY)表信息(后面演示用):mysql> show create table t_a;——+| t_a | CREATE TABLE t_a ( id bigint(20) NOT NULL DEFAULT ‘0’, age int(20) DEFAULT NULL, code int(20) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_code (code), KEY age_key (age)) ENGINE=InnoDB DEFAULT CHARSET=gbk |+——-+—————————————–+1 row in set (0.03 sec)SIMPLE:简单SELECT(不使用UNION或子查询等)mysql> explain select * from t_a where id =1;+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+| 1 | SIMPLE | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+1 row in set, 1 warning (0.03 sec)PRIMARY:嵌套查询时最外层的查询mysql> explain select * from t_a where num >(select num from t_a where id = 3);+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+| 1 | PRIMARY | t_a | NULL | range | num_key | num_key | 5 | NULL | 6 | 100.00 | Using where; Using index || 2 | SUBQUERY | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+2 rows in set, 1 warning (0.03 sec)UNION:UNION中的第二个或后面的SELECT语句mysql> explain select * from t_a where id =9 union all select * from t_a;+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————-+| 1 | PRIMARY | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL || 2 | UNION | t_a | NULL | index | NULL | num_key | 5 | NULL | 9 | 100.00 | Using index |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————-+2 rows in set, 1 warning (0.04 sec)DEPENDENT UNION:UNION中的第二个或以后的SELECT语句,取决于外部查询mysql> explain select * from t_a where id in (select id from t_a where id >8 union all select id from t_a where id =5);+—-+——————–+——-+————+——–+—————+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+——————–+——-+————+——–+—————+———+———+——-+——+———-+————————–+| 1 | PRIMARY | t_a | NULL | index | NULL | num_key | 5 | NULL | 9 | 100.00 | Using where; Using index || 2 | DEPENDENT SUBQUERY | t_a | NULL | eq_ref | PRIMARY | PRIMARY | 8 | func | 1 | 100.00 | Using where; Using index || 3 | DEPENDENT UNION | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | Using index |+—-+——————–+——-+————+——–+—————+———+———+——-+——+———-+————————–+3 rows in set, 1 warning (0.08 sec)UNION RESULT:UNION的结果mysql> explain select num from t_a where id = 3 union select num from t_a where id =4;+—-+————–+————+————+——-+—————+———+———+——-+——+———-+—————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————–+————+————+——-+—————+———+———+——-+——+———-+—————–+| 1 | PRIMARY | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL || 2 | UNION | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL || NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |+—-+————–+————+————+——-+—————+———+———+——-+——+———-+—————–+3 rows in set, 1 warning (0.03 sec)SUBQUERY:子查询中的第一个选择mysql> explain select * from t_a where num >(select num from t_a where id = 3);+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+| 1 | PRIMARY | t_a | NULL | range | num_key | num_key | 5 | NULL | 6 | 100.00 | Using where; Using index || 2 | SUBQUERY | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+2 rows in set, 1 warning (0.03 sec)DEPENDENT SUBQUERY:子查询中的第一个选择,取决于外部查询mysql> explain select * from t_a where num in(select num from t_a where id = 3 union select num from t_a where id =4);+—-+——————–+————+————+——-+—————–+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+——————–+————+————+——-+—————–+———+———+——-+——+———-+————————–+| 1 | PRIMARY | t_a | NULL | index | NULL | num_key | 5 | NULL | 9 | 100.00 | Using where; Using index || 2 | DEPENDENT SUBQUERY | t_a | NULL | const | PRIMARY,num_key | PRIMARY | 8 | const | 1 | 100.00 | NULL || 3 | DEPENDENT UNION | t_a | NULL | const | PRIMARY,num_key | PRIMARY | 8 | const | 1 | 100.00 | NULL || NULL | UNION RESULT | <union2,3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |+—-+——————–+————+————+——-+—————–+———+———+——-+——+———-+————————–+4 rows in set, 1 warning (0.12 sec)DERIVED:派生表(子查询中产生的临时表)mysql> explain select a.id from (select id from t_a where id >8 union all select id from t_a where id =5) a;+—-+————-+————+————+——-+—————+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+————+————+——-+—————+———+———+——-+——+———-+————————–+| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL || 2 | DERIVED | t_a | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 1 | 100.00 | Using where; Using index || 3 | UNION | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | Using index |+—-+————-+————+————+——-+—————+———+———+——-+——+———-+————————–+3 rows in set, 1 warning (0.12 sec)2.3 table显示这一行的数据是关于哪张表的,有时是真实的表名字,有时也可能是以下几种结果<unionM,N>: 指id为M,N行结果的并集<derivedN>: 该行是指id值为n的行的派生表结果。派生表可能来自例如from子句中的子查询。<subqueryN>: 该行是指id值为n的行的物化子查询的结果。2.4 partitions查询的记录所属于的分区,对于未分区表,该值为NULL。2.5 type连接使用了哪种类别,有无使用索引,常用的类型有:system, const, eq_ref, ref, range, index, ALL(从左到右,性能越来越差),详情查看 EXPLAIN Join TypesNULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成system:这个表(也可能是查询出来的临时表)只有一行数据 (= system table). 是const中的一个特例const:表最多有一个匹配行,它将在查询开始时被读取。因为仅有一行,在这行的列值可被优化器剩余部分认为是常数。const表很快,因为它们只读取一次!const用于查询条件为PRIMARY KEY或UNIQUE索引并与常数值进行比较时的所有部分。 在下面的查询中,tbl_name可以用于const表:SELECT * from tbl_name WHERE primary_key=1;SELECT * from tbl_name WHERE primary_key_part1=1和 primary_key_part2=2;–例子mysql> explain select * from t_a where id =1;+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+| 1 | SIMPLE | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+1 row in set, 1 warning (0.07 sec)eq_ref:对于前几个表中的每一行组合,从该表中读取一行。除了system和const,这是最好的连接类型。当连接使用索引的所有部分,并且索引是主键或唯一非空索引时,将使用它。eq_ref可以用于使用= 操作符比较的带索引的列。比较值可以为常量或一个使用在该表前面所读取的表的列的表达式。在下面的例子中,MySQL可以使用eq_ref联接去处理ref_tables:SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;–例子(t_b为t_a的复制表,表结构相同)mysql> explain select * from t_a,t_b where t_a.code=t_b.code;+—-+————-+——-+————+——–+—————+———+———+—————+——+———-+——-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——–+—————+———+———+—————+——+———-+——-+| 1 | SIMPLE | t_a | NULL | ALL | uk_code | NULL | NULL | NULL | 9 | 100.00 | NULL || 1 | SIMPLE | t_b | NULL | eq_ref | uk_code | uk_code | 4 | test.t_a.code | 1 | 100.00 | NULL |+—-+————-+——-+————+——–+—————+———+———+—————+——+———-+——-+2 rows in set, 1 warning (0.03 sec)ref对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取。如果联接只使用键的最左边的前缀,或如果键不是UNIQUE或PRIMARY KEY(换句话说,如果联接不能基于关键字查询结果为单个行的话),则使用ref。如果使用的键仅仅匹配少量行,该联接类型是不错的。ref可以用于使用=或<=>操作符的带索引的列。在下面的例子中,MySQL可以使用ref联接来处理ref_tables:SELECT * FROM ref_table WHERE key_column=expr;SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;–例子(t_b为t_a的复制表,表结构相同)mysql> explain select * from t_a,t_b where t_a.age=t_b.age;+—-+————-+——-+————+——+—————+———+———+————–+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——+—————+———+———+————–+——+———-+————-+| 1 | SIMPLE | t_a | NULL | ALL | age_key | NULL | NULL | NULL | 9 | 100.00 | Using where || 1 | SIMPLE | t_b | NULL | ref | age_key | age_key | 5 | test.t_a.age | 1 | 100.00 | NULL |+—-+————-+——-+————+——+—————+———+———+————–+——+———-+————-+2 rows in set, 1 warning (0.03 sec)fulltext:使用FULLTEXT索引执行连接ref_or_null:该联接类型ref类似,但是添加了MySQL可以专门搜索包含NULL值的行。在解决子查询中经常使用该联接类型的优化。 在下面的例子中,MySQL可以使用ref_or_null联接来处理ref_tables:SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;–例子mysql> explain select * from t_a where t_a.age =3 or t_a.age is null;+—-+————-+——-+————+————-+—————+———+———+——-+——+———-+———————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+————-+—————+———+———+——-+——+———-+———————–+| 1 | SIMPLE | t_a | NULL | ref_or_null | age_key | age_key | 5 | const | 2 | 100.00 | Using index condition |+—-+————-+——-+————+————-+—————+———+———+——-+——+———-+———————–+1 row in set, 1 warning (0.03 sec)index_merge:该联接类型表示使用了索引合并优化方法。在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素。SELECT * FROM ref_table WHERE idx1=expr1 OR idx2 =expr2;–例子mysql> explain select * from t_a where t_a.code =3 or t_a.age = 3;+—-+————-+——-+————+————-+—————–+—————–+———+——+——+———-+——————————————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+————-+—————–+—————–+———+——+——+———-+——————————————-+| 1 | SIMPLE | t_a | NULL | index_merge | uk_code,age_key | uk_code,age_key | 4,5 | NULL | 2 | 100.00 | Using union(uk_code,age_key); Using where |+—-+————-+——-+————+————-+—————–+—————–+———+——+——+———-+——————————————-+1 row in set, 1 warning (0.03 sec)unique_subquery:该类型替换了下面形式的IN子查询的ref:value IN (SELECT primary_key FROM single_table WHERE some_expr) unique_subquery是一个索引查找函数,可以完全替换子查询,效率更高。index_subquery:该联接类型类似于unique_subquery。可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引:value IN (SELECT key_column FROM single_table WHERE some_expr)range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。key_len包含所使用索引的最长关键元素。在该类型中ref列为NULL。当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用rangemysql> explain select * from t_a where id > 8;+—-+————-+——-+————+——-+—————+———+———+——+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——+——+———-+————-+| 1 | SIMPLE | t_a | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 1 | 100.00 | Using where |+—-+————-+——-+————+——-+—————+———+———+——+——+———-+————-+1 row in set, 1 warning (0.03 sec)index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。当查询只使用作为单索引一部分的列时,MySQL可以使用该联接类型。ALL:对于每个来自于先前的表的行组合,进行完整的表扫描。如果表是第一个没标记const的表,这通常不好,并且通常在它情况下很差。通常可以增加更多的索引而不要使用ALL,使得行能基于前面的表中的常数值或列值被检索出。2.6 possible_keyspossible_keys列指出MySQL能使用哪个索引在该表中找到行。注意,该列完全独立于EXPLAIN输出所示的表的次序。这意味着在possible_keys中的某些键实际上不能按生成的表次序使用。如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询2.7 keykey列显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。2.8 key_lenkey_len列显示MySQL决定使用的键长度。如果键是NULL,则长度为NULL。 使用的索引的长度。在不损失精确性的情况下,长度越短越好2.9 refref列显示使用哪个列或常数与key一起从表中选择行。2.10 rowsrows列显示MySQL认为它执行查询时必须检查的行数。2.11 Extra该列包含MySQL解决查询的详细信息,下面详细.Distinct:一旦MYSQL找到了与行相联合匹配的行,就不再搜索了Not exists:MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了Range checked for each:没有找到理想的索引,因此对于从前面表中来的每一个行组合,MYSQL检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一Using filesort:看到这个的时候,查询就需要优化了。MYSQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行Using index:列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候Using temporary:看到这个的时候,查询需要优化了。这里,MYSQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上Using where:使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题参考:MySQL5.7 EXPLAIN Output Format ...

April 1, 2019 · 8 min · jiezi