美文网首页
存储架构优化的那些事(一)

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

作者: 勤_ | 来源:发表于2021-08-27 17:08 被阅读0次
    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: 查询涉及到的表.

      表示查询涉及的表或衍生表,直接显示表名或者表的别名。
      <unionM,N> 由 ID 为 M,N 查询 union 产生的结果
      <subqueryN> 由 ID 为 N 查询产生的结果
      <derivedx> 从衍生表中查数据,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官网优化策略

    相关文章

      网友评论

          本文标题:存储架构优化的那些事(一)

          本文链接:https://www.haomeiwen.com/subject/qfwmiltx.html