美文网首页
SQL索引-高性能策略, 2022-06-18

SQL索引-高性能策略, 2022-06-18

作者: Mc杰夫 | 来源:发表于2022-06-18 14:37 被阅读0次

    (2022.06.18 Sat)
    在索引使用过程中,需要注意诸多问题,这里列举若干。

    评价索引设置的优劣,由L&L提出了three-star system,星级越高越好:

    • 一星:索引将相关的记录放到一起
    • 二星:索引中的数据顺序和查询中的排列顺序一致
    • 三星:索引中的列包含了查询中的全部列

    不参与运算的列

    WHERE从句中的索引不能是表达式的一部分或函数的参数。比如下面这个查询无法使用作为检索条件的索引。

    mysql> SELECT actor_id FROM actor WHERE actor_id + 1 > 4;
    

    MySQL无法解析这个WHERE从句中的语句,尽管轻易得知actor_id的范围。为保证使用索引,应将索引列放在运算符号的一侧。

    下面这种将索引列作为函数参数的方式也无法使用索引。

    mysql> SELECT ... WHERE TO_DAYS(current_date) - TO_DAYS(date_poop) < 10;
    

    前缀索引和索引选择性

    长字符串的列被用作索引,会导致索引变大变慢,这种列除了使用哈希索引,还可以对前缀做索引。

    使用部分字符做索引,可大大节省索引空间,但可能导致索引选择性降低。索引的选择性,是不重复的索引值(基数,cardinality)与数据表的记录总数的比值。该值从0趋近于1,越接近于1则查询效率越高,因选择性高的索引可以在查找是过滤掉更多的行。最好的索引其选择性是1,效率最高,性能最好。

    对于字符型列的前缀选取,重点是在选择性和前缀长度之间做出平衡。前缀长度增加显然增强选择性,但是会浪费索引空间,以此类推。如果前缀的基数接近完整列的基数,则最佳。

    为找到最适合的前缀长度,首先可以查看最常见值的情况,并和最常见的前缀值进行比较。

    mysql> SELECT COUNT(*) as cnt, city FROM cities 
           GROUP BY city ORDER BY cnt DESC LIMIT 10;
    

    返回城市名字出现次数的频度。

    接下来对前缀进行统计,使用MySQL的LEFT(vol_name, n)找出vol_name字段每个值的前n为字符。

    mysql> SELECT COUNT(*), LEFT(city, 3) as ref FROM cities
           GROUP BY cnt ORDER BY cnt DESC LIMIT 10;
    

    只用3位查询,结果可能会比较大,可以将不同长度的前缀的统计结果都展示出来。首先查看整个列表的完整性指标

    mysql> SELECT COUNT(DISTINCT city)/COUNT(*) FROM cities;
    

    之后查看不同长度前缀的完整性指标

    SELECT COUNT(DISTINCT LEFT(city, 3)) as sel3,
           COUNT(DISTINCT LEFT(city, 4)) as sel4,
           COUNT(DISTINCT LEFT(city, 5)) as sel5,
           COUNT(DISTINCT LEFT(city, 6)) as sel6,
           COUNT(DISTINCT LEFT(city, 7)) as sel7
    FROM cities;
    

    从不同长度的结果中发现长度越长,则完整性指标越接近整个列表的指标。因而在长度变长后完整性基本不变时可以确定长度。

    比如这个案例中可能确定长度最佳为7,则将cities列的前7位作为索引,使用下面指令将其加为索引

    mysql> ALTER TABLE cities ADD KEY (city(7));
    

    前缀索引的缺点是,无法使用前缀列做ORDER BYGROUP BY,也无法做覆盖索引。

    多列单独索引

    给一个关系中的多个列分别单独建立索引,并不一定能提升效率。比如在下面查询中,尽管WHERE从句标明了被索引的列actor_idfilm_id,但是系统仍然执行全盘扫描。

    SELECT film_id, actor_id FROM movies
    WHERE actor_id = 3 OR film_id = 3;
    

    除非使用UNION命令单独对索引列做查询条件

    SELECT film_id, actor_id FROM movies
    WHERE actor_id = 3
    UNION ALL
    SELECT film_id, actor_id FROM movies
    WHERE film_id = 3 AND actor_id <> 1;
    

    可通过EXPLAIN <SQL commands>的方式查看查询的基本信息,特别是其中的Extra列的结果。

    mysql> EXPLAIN SELECT film_id, actor_id FROM movies
    WHERE actor_id = 3 OR film_id = 3;
    

    选择合适的索引顺序

    在多列的B-tree索引中,索引列的顺序意味着索引按照从左到右的顺序依次排序。

    多列索引的一个经验是将选择性最高的列放在前面。此时索引的作用只是优化WHERE条件查找。然而查询性能不止依赖索引列的选择性,也和值分布有关。

    分析下面案例

    SELECT * FROM payment WHERE staff_id = 2 AND customer_id = 1000;
    

    其中的两个字段哪个应该在前面?我们先来看各自有多少个。

    SELECT SUM(staff_id=2) ss, SUM(customer_id=1000) sc FROM payment;
    

    返回结果

    *********************1. row **********************
    SUM(staff_id=2) = 7992
    SUM(customer_id=1000) = 30
    

    这个分析结果显示customer_id=1000返回的结果远小于staff_id=2的结果数目,显然customer_id=1000放在前面的检索效果更好。为验证这个结果,我们查看满足customer_id条件的staff_id数目。

    mysql> SELECT SUM(staff_id=2) WHERE customer_id=1000;
    *********************1. row **********************
    SUM(staff_id=2) = 17
    

    但是这个案例的分析仅仅是基于单个数据,无法代表整体数据的分布情况。鉴于此,可以查看全局基数和选择性。

    SELECT COUNT(DISTINCT staff_id)/COUNT(*) as sid_selectivity,
    COUNT(DISTINCT customer_id)/COUNT(*) as cid_selectivity,
    COUNT(*)
    FROM payment;
    
    *********************1. row **********************
    sid_selectivity: 0.0001
    cid_selectivity: 0.0373
          COUNT(*): 16049
    

    可见customer_id的选择性更高,可作为索引的第一列

    mysql> ALTER TABLE payment ADD KEY(customer_id, staff_id);
    

    聚簇索引 Cluster index

    (2022.06.20 Mon)
    不同于前面提到的索引,聚簇索引不单是一种索引类型,更是一种数据存储方式。InnoDB中的聚簇索引在同一结构中保存了B-tree索引和数据行。存储引擎负责实现索引,并非所有引擎都支持索引。

    如下所示的聚簇索引,在聚簇索引每个叶子页(leaf page)上,保存的是该索引对应的数据行。相比之下,索引的叶子页上保存的是该索引对应的数据的指针。实际上,cluster聚簇的意思就是数据行和对应的键值存储在一起。


    data distribution of cluster index

    无法同时把数据存在两个地方,因此一个表只能有一个聚簇索引。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有,InnoDB会隐式的定义一个主键作为聚簇索引。

    优缺点

    优点:

    • 把相关数据保存在一起,只需要读取少数数据页就可以获得所需数据,而没有使用聚簇索引可能导致磁盘I/O
    • 数据访问更快,叶子页保存键值和数据,不需要普通索引中从指针读取数据的步骤
    • 覆盖索引扫描的查询可以直接使用叶节点中的主键

    缺点:

    • 提高了I/O密集型应用的性能,但如果数据都存放在内存,则聚簇索引没有优势
    • 插入速度依赖于插入顺序。按主键顺序插入是加载数据到InnoDB最快的方式,如果没有按主键顺序加载,则需使用OPTIMIZE TABLE组织一下表
    • 更新聚簇索引的代价高,因为会强制将被更新的行移动到新的位置
    • 插入新行可能面临页分离(page split)的问题,会导致表占用更多的磁盘空间
    • 可能导致全盘搜索变慢,尤其是行稀疏的时候,或者分裂导致数据存在不同页的时候
    • 二级索引,即非聚簇索引可能比想像的更大,因二级索引的叶子结点保存了引用行的键值。

    关于二次索引:二次索引需要两次索引查找,而非一次。二次索引也通过B-tree保存,其叶子节点保存的是行的主键值,而非指向行的物理位置的指针。通过二次索引查找行,存储引擎需要找到二次索引中叶子节点对应的主键值,再根据这个值去聚簇索引中找到对应的行。这里使用了两次B-tree查找。

    聚簇索引(InnoDB)和非聚簇索引(MyISAM)的数据分布对比

    Data distribution of Cluster index and Non-cluster index

    在聚簇索引中按主键顺序插入行

    如果InnoDB中没有数据需要聚集,可定义一个代理键surrogate key作为主键,其数据和应用无关。最简单的方法是使用自增序列AUTO_INCREMENT,可保证数据行按顺序写入,做关联操作的性能也会更好。

    最好避免随机的,即不连续且值分布范围大的聚簇索引,特别是对于I/O密集型的应用,使得数据没有聚集性。

    (2022.05.06 Fri)
    聚集索引和非聚集索引的差别在于:聚集索引的物理顺序和逻辑顺序相同,而非聚集不同。一个关系只能有一个聚集索引,可以有多个非聚集索引。
    聚集索引
    一个关系中的主键Primary key就是该关系的索引。建立了主键/索引的关系,从一行行整齐但无序排列在硬盘上的结构变成了一个平衡树/B+树结构,这个关系变成了一个索引,也就是聚集索引。这解释了一个关系为什么只能有一个主键/聚集索引,却可以有多个非聚集索引。聚集索引把数据关系转变成平衡树的结构。

    在聚集索引的平衡树中,所有节点都由主键字段中数据构成,即主键id字段。例如我们查询某个关系中主键id=199对应的记录,假设该平衡树保存了10000个记录,高度为3。通过平衡树找到id=199对应的叶节点,再通过该叶节点找到相应的数据行。

    下面分析索引对查询效率的提升。上面例子中,平衡树高度为3,树的高度决定了IO开销,即查询3次可查到对应的数据。但是如果不使用平衡树结构,则最坏情况下需要遍历10000次匹配/IO开销才能找到所查询的结果。这里的效率对比是3次IO开销和10000次IO开销。设平衡树的每层树分支个数为b,关系中记录总数为n,则不经过索引查询特定记录的复杂度是O(n),而经过索引/平衡树处理后的查询复杂度为O(log_b n)。可见索引使得查询效率极大提升。

    经过聚集索引处理的关系,查询速度得到显著提升,但是每次对关系的更新都会导致平衡树/索引的重新构建,带来额外的工作量。

    非聚集索引
    同样采用平衡树作为数据结构。索引树结构中各节点的值来⾃于表中的索引字段。每次给字段建立⼀个新索引, 字段中的数据就会被复制⼀份出来, ⽤于⽣成索引。 因此, 给表添加索引,会增加表的体积, 占⽤磁盘存储空间。

    ⾮聚集索引和聚集索引的区别在于,通过聚集索引可以查到需要查找的数据,⽽通过⾮聚集索引可以查到记录对应的主键值,再使⽤主键的值通过聚集索引查找到需要的数据。不管以何种方式查询
    ,都会利⽤主键通过聚集索引来定位到数据,聚集索引/主键是通往真实数据所在的唯⼀路径。

    Reference

    1 高性能MySQL第三版,宁等翻译,电子工业出版社

    相关文章

      网友评论

          本文标题:SQL索引-高性能策略, 2022-06-18

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