概述
对于绝大多数的软件应用性能优化,存储架构的优化是不可能逃避的且非常重要的一环。所以了解存储组件语法优化、存储组件优化分析及存储架构的优化就很有必要了。基于Mysql,为了跑的稳,跑得快 。
-
Mysql SQL语法优化分析~那些方面
Explain
Optimizer_trace
-
Mysql组件优化分析~那些方面
整体架构
DML原理
索引原理
锁原理
事务原理
内存分析\CPU分析
-
存储架构分析~那些方面
数据库读写分离
数据库分库分表
数据库分布式事务
主备&主从架构
双机切换架构
集群选举架构
分片架构
分区架构
从了解清楚Explain相关内容开始。
工具及版本
工具 | 版本 |
---|---|
mysql | 5.7 |
初始化脚本
通过如下脚本,在mysql数据库中新建对应的实验表和数据。
CREATE TABLE `user_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '',
`age` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name_index` (`name`)
)
ENGINE = INNODB
DEFAULT CHARSET = utf8;
INSERT INTO user_info (NAME, age) VALUES ('xys', 20);
INSERT INTO user_info (NAME, age) VALUES ('a', 21);
INSERT INTO user_info (NAME, age) VALUES ('b', 23);
INSERT INTO user_info (NAME, age) VALUES ('c', 50);
INSERT INTO user_info (NAME, age) VALUES ('d', 15);
INSERT INTO user_info (NAME, age) VALUES ('e', 20);
INSERT INTO user_info (NAME, age) VALUES ('f', 21);
INSERT INTO user_info (NAME, age) VALUES ('g', 23);
INSERT INTO user_info (NAME, age) VALUES ('h', 50);
INSERT INTO user_info (NAME, age) VALUES ('i', 15);
CREATE TABLE `order_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_id` BIGINT(20) DEFAULT NULL,
`product_name` VARCHAR(50) NOT NULL DEFAULT '',
`productor` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`)
)
ENGINE = INNODB
DEFAULT CHARSET = utf8;
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p2', 'WL');
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'DX');
INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p5', 'WL');
INSERT INTO order_info (user_id, product_name, productor) VALUES (3, 'p3', 'MA');
INSERT INTO order_info (user_id, product_name, productor) VALUES (4, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (6, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (9, 'p8', 'TE');
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p199', 'WHH99');
-- 验证分区
CREATE TABLE user_temp (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20),
PRIMARY KEY (id)
)ENGINE = INNODB PARTITION BY KEY (id) PARTITIONS 3;
INSERT INTO user_temp VALUES(1,"hello") ;
INSERT INTO user_temp VALUES(2,"world") ;
INSERT INTO user_temp VALUES(3,"nice") ;
DROP PROCEDURE IF EXISTS batchInsert;
-- 批量插入数据
DELIMITER //
CREATE PROCEDURE batchInsert()
BEGIN
DECLARE num INT;
SET num=20;
WHILE num<=100000 DO
INSERT INTO user_info (id, NAME, age) VALUES (num ,CONCAT('xys',num) , 20);
INSERT INTO order_info (user_id, product_name, productor) VALUES (num, CONCAT('p',num) , CONCAT('WHH',num));
SET num=num+1;
END WHILE;
END
//
DELIMITER ; #恢复;表示结束
CALL batchInsert;
SELECT COUNT(1) FROM `user_info`;
SELECT COUNT(1) FROM `order_info`;
-- user_info增加一列
ALTER TABLE user_info ADD COLUMN `oid` BIGINT(20);
UPDATE user_info u , order_info o SET u.oid = o.user_id WHERE o.user_id = u.id ;
接下来逐一分析以及解释Explain Sql的各种典型情况。
SQL-Explain优化分析
Explain各列的含义如下:
-
id: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符.
id相同执行顺序由上至下。 id不同,id值越大优先级越高,越先被执行。 id为 null 时表示一个结果集,不需要使用它查询,常出现在包含 union等查询语句中
例子:
EXPLAIN (SELECT * FROM user_info u1 WHERE u1.id IN (1, 2, 3)) UNION (SELECT * FROM user_info u2 WHERE u2.id IN (3, 4, 5));
-
select_type: SELECT 查询的类型. 它的常用取值有:
SQL要尽量出现避免UNION、DEPENDENT UNION、DEPENDENT SUBQUERY、SUBQUERY、MATERIALIZED出现这些字样。
SIMPLE:简单的 select 查询,查询中不包含子查询或者UNION
PRIMARY:为复杂查询创建的首要表(也是最外层的表) 这种类型通常与DERIVED 或者 UNION 混合使用见到
UNION: union中的第二个或随后的select查询,不依赖于外部查询的结果集
DEPENDENT UNION: union中的第二个或随后的select查询,依赖于外部查询的结果集
UNION RESULT: 出现在UNION 或UNION ALL语句中 代表把所有结果集联合起来
SUBQUERY: 子查询中的第一个select查询,不依赖与外部查询的结果集
DEPENDENT SUBQUERY:子查询中的第一个select查询,依赖于外部查询的结果集
DERIVED: 衍生表当查询使用内联视图时会出现此关键字, 用于from子句中有子查询的情况,mysql会递归执行这些子查询,此结果集放在临时表中
-
MATERIALIZED: 子查询物化 ,Semi-join Materialization优化策略是将半连接的子查询物化为临时表
例子
-- MATERIALIZED ALTER TABLE user_info ADD INDEX idx_oid(oid); EXPLAIN SELECT o.id FROM order_info o WHERE o.user_id IN (SELECT oid FROM user_info WHERE oid <86999 ) ; ALTER TABLE user_info DROP INDEX idx_oid;
UNCACHEABLE SUBQUERY:表示子查询不可被物化需要逐次运行
UNCACHEABLE UNION: 子查询中出现UNION并且不可被缓存在UNION 后的 SELECT 语句出现此关键词
例子:
-- SUBQUERY EXPLAIN SELECT * FROM order_info WHERE id = (SELECT id FROM order_info u2 WHERE u2.id =1 ) ;
-- DEPENDENT SUBQUERY 情况分析 EXPLAIN SELECT o.* , (SELECT u2.id FROM user_info u2 WHERE o.user_id = u2.id) AS uid FROM order_info o; EXPLAIN SELECT o.* FROM order_info o WHERE EXISTS ( SELECT 1 FROM user_info u2 WHERE o.user_id = u2.id AND u2.id IN (3, 4, 5));
-- DEPENDENT SUBQUERY / DEPENDENT UNION 情况分析 EXPLAIN SELECT o.* FROM order_info o WHERE o.user_id IN ( SELECT u2.id FROM user_info u2 WHERE u2.id IN (3, 4, 5) UNION SELECT u3.id FROM user_info u3 WHERE u3.id IN (6, 7, 8) )
-
table: 查询涉及到的表.
表示查询涉及的表或衍生表,直接显示表名或者表的别名。
由 ID 为 M,N 查询 union 产生的结果 由 ID 为 N 查询产生的结果 从衍生表中查数据,x 表示对应的执行计划id -
partitions: 匹配的分区, 表分区、表创建的时候可以指定通过那个列进行表分区。
例子:
EXPLAIN SELECT id FROM user_temp WHERE id = 1;
-
type: join 类型
`type` 字段比较重要, 它提供了判断查询是否高效的重要依据依据. 通过 `type` 字段, 我们判断此次查询是 `全表扫描` 还是 `索引扫描` 等. #### type 类型的性能比较 通常来说, 不同的 type 类型的性能关系如下: `ALL < index < range ~ index_merge < ref < eq_ref < const < system` `ALL` 类型因为是全表扫描, 因此在相同的查询条件下, 它是速度最慢的. 而 `index` 类型的查询虽然不是全表扫描, 但是它扫描了所有的索引, 因此比 ALL 类型的稍快. 后面的几种类型都是利用了索引来查询数据, 因此可以过滤部分或大部分数据, 因此查询效率就比较高了.
type 常用的取值有:
system: 表中只有一条数据. 这个类型是特殊的
const
类型.const: 针对主键或唯一索引的等值查询扫描, 最多只返回一行数据. const 查询速度非常快, 因为它仅仅读取一次即可.
例如下面的这个查询, 它使用了主键索引, 因此type
就是const
类型的.eq_ref: 此类型通常出现在多表的 join 查询, 表示对于前表的每一个结果, 都只能匹配到后表的一行结果. 并且查询的比较操作通常是
=
, 查询效率较高ref: 此类型通常出现在多表的 join 查询, 针对于非唯一或非主键索引, 或者是使用了
最左前缀
规则索引的查询.range: 表示使用索引范围查询, 通过索引字段范围获取表中部分数据记录. 这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中.
当type
是range
时, 那么 EXPLAIN 输出的ref
字段为 NULL, 并且key_len
字段是此次查询中使用到的索引的最长的那个.-
index: 表示全索引扫描(full index scan), 和 ALL 类型类似, 只不过 ALL 类型是全表扫描, 而 index 类型则仅仅扫描所有的索引, 而不扫描数据.
index 类型通常出现在: 所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据. 当是这种情况时, Extra 字段 会显示Using index
.例子:
EXPLAIN SELECT id FROM user_temp
ALL: 表示全表扫描, 这个类型的查询是性能最差的查询之一. 通常来说, 我们的查询不应该出现 ALL 类型的查询, 因为这样的查询在数据量大的情况下, 对数据库的性能是巨大的灾难. 如一个查询是 ALL 类型查询, 那么一般来说可以对相应的字段添加索引来避免.
-
possible_keys: 此次查询中可能选用的索引
`possible_keys` 表示 MySQL 在查询时, 能够使用到的索引. 注意, 即使有些索引在 `possible_keys` 中出现, 但是并不表示此索引会真正地被 MySQL 使用到. MySQL 在查询时具体使用了哪些索引, 由 `key` 字段决定.
-
key: 此次查询中确切使用到的索引. 表示查询优化器使用了索引的字节数. 这个字段可以评估组合索引是否完全被使用, 或只有最左部分字段被使用到.
此字段是 MySQL 在当前查询时所真正使用到的索引.
-
key_len: 表示查询优化器使用了索引的字节数. 这个字段可以评估组合索引是否完全被使用, 或只有最左部分字段被使用到.
key_len 的计算规则如下: - 字符串 - char(n): n 字节长度 - varchar(n): 如果是 utf8 编码, 则是 3 * n + 2字节; 如果是 utf8mb4 编码, 则是 4 * n + 2 字节. - 数值类型: - TINYINT: 1字节 - SMALLINT: 2字节 - MEDIUMINT: 3字节 - INT: 4字节 - BIGINT: 8字节 - 时间类型 - DATE: 3字节 - TIMESTAMP: 4字节 - DATETIME: 8字节 - 字段属性: NULL 属性 占用一个字节. 如果一个字段是 NOT NULL 的, 则没有此属性.
例子:
-- KEY_LEN EXPLAIN SELECT o.* FROM order_info o WHERE o.user_id IN ( SELECT u2.id FROM user_info u2 WHERE u2.id IN (3, 4, 5) UNION SELECT u3.id FROM user_info u3 WHERE u3.id IN (6, 7, 8) );
ref: 哪个字段或常数与 key 一起被使用
rows: 显示此查询一共扫描了多少行. 这个是一个估计值. rows 也是一个重要的字段. MySQL 查询优化器根据统计信息, 估算 SQL 要查找到结果集需要扫描读取的数据行数. 这个值非常直观显示 SQL 的效率好坏, 原则上 rows 越少越好.
filtered: 表示此查询条件所过滤的数据的百分比。
-
extra: 额外的信息,EXplain中的很多额外的信息会在 Extra 字段显示, 常见的有以下几种内容:
SQL要尽量出现避免Using Filesort、Using temporary、Using join buffer (Block Nested Loop)等情况。
Using Filesort:MySQL对数据使用一个外部的文件内容进行了排序,而不是按照表内的索引进行排序读取.
Using temporary:使用临时表保存中间结果,也就是说 MySQL 在对查询结果排序时使用了临时表,常见于 order by 或 group by.
Using index:表示 SQL 操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高.
Using index condition:表示 SQL 操作命中了索引,但不是所有的列数据都在索引树上,还需要访问实际的行记录.
Using where:表示 SQL 操作使用了 where 过滤条件.
Select tables optimized away:基于索引优化 MIN/MAX 操作或者 MyISAM 存储引擎优化 COUNT(*) 操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即可完成优化.
-
FirstMatch:这是在处理半连接子查询时可能会用到的一种Semi-join优化策略。
例子:
-- FirstMatch EXPLAIN SELECT o.id FROM order_info o WHERE o.user_id IN (SELECT oid FROM user_info WHERE oid = 26999 ) ;
-
Using join buffer (Block Nested Loop):表示 SQL 操作使用了关联查询或者子查询,且需要进行嵌套循环计算.
例子:
-- Using join buffer (Block Nested Loop) ALTER TABLE order_info DROP INDEX user_product_detail_index; EXPLAIN SELECT * FROM order_info o , user_info u WHERE u.age >20 AND o.user_id = u.oid AND o.product_name LIKE 'p%'; ALTER TABLE order_info ADD INDEX `user_product_detail_index` (`user_id`, `product_name`, `productor`);
参考
Mysql官网优化策略