存储架构优化的那些事(一)

mysql.jpeg

概述

对于绝大多数的软件应用性能优化,存储架构的优化是不可能逃避的且非常重要的一环。所以了解存储组件语法优化、存储组件优化分析及存储架构的优化就很有必要了。基于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));
    
    image-20210827105854487.png
  • 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;
      
      image-20210827163545948.png
    • 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 ) ;
    
    image-20210827134252555.png
    -- 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));
    
    image-20210827112503651.png
    -- 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)
    )
    
    image-20210827110409266.png
  • table: 查询涉及到的表.

    表示查询涉及的表或衍生表,直接显示表名或者表的别名。
     由 ID 为 M,N 查询 union 产生的结果
     由 ID 为 N 查询产生的结果
     从衍生表中查数据,x 表示对应的执行计划id
    
  • partitions: 匹配的分区, 表分区、表创建的时候可以指定通过那个列进行表分区。

    例子:

    EXPLAIN SELECT id FROM user_temp WHERE id = 1;
    
    image-20210827105552412.png
  • 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() 操作中.
      typerange 时, 那么 EXPLAIN 输出的 ref 字段为 NULL, 并且 key_len 字段是此次查询中使用到的索引的最长的那个.

    • index: 表示全索引扫描(full index scan), 和 ALL 类型类似, 只不过 ALL 类型是全表扫描, 而 index 类型则仅仅扫描所有的索引, 而不扫描数据.
      index 类型通常出现在: 所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据. 当是这种情况时, Extra 字段 会显示 Using index.

      例子:

      EXPLAIN SELECT id FROM user_temp
      
      image-20210827105231076.png
    • 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)
    );
    
    image-20210827143619457.png
  • 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 ) ;
      
      image-20210827164131193.png
    • 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`);
      
      image-20210827150108619.png

参考

Mysql官网优化策略

你可能感兴趣的:(存储架构优化的那些事(一))