聊聊数据库中的烂索引

本文涉及的产品
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: 索引是数据库中用于加速查询的常用组件,它通过对数据冗余和重组织来加速SQL查询。

作者:未启  本文来源:PolarDB-X知乎号

背景

索引是数据库中用于加速查询的常用组件,它通过对数据冗余和重组织来加速SQL查询。通常来说,恰当的索引可以提升系统的查询性能。 关于索引存在一些误解,如:索引总是能提升查询性能,因此索引越多越好,比如下图中的例子

20230816195158.jpg

只看收益,不看代价是不行的。分布式数据库系统一般支持两类索引:由分布式全局事务维护的全局索引、由本地事务维护的本地索引。这两类索引都会不同程度影响系统的写入性能,下图展示了建立不同数量的索引时,对系统的写入性能的影响。

20230816195305.jpg可以看出创建1个全局索引,就会使系统的写入性能降低至原来的约30%;单看MySQL,在创建8个索引(本地索引)的情况下,写入性能会降低至原来的85%(引用自我们的历史文章TiDB、OceanBase、PolarDB-X、CockroachDB二级索引写入性能测评,感兴趣的读者可深入阅读)。 因此,在我们享受索引带来的查询加速收益时,还需关注其引入的维护开销。特别是当引入一个索引没能带来预期收益、或者带来的开销远大于其带来的查询加速收益时,索引反而成为一种负担。我们称这类索引为烂索引,避开它们可以帮助数据库获得更好的写入性能。 回顾文章开头举例的表 warehouse,你能看出其中有几个烂索引吗?我们先讨论一下应用中常见的烂索引,然后在文末公布答案。

低频访问索引和许久未访问索引

新建的索引并未按照预期目的被数据库优化器使用时,就是一个烂索引,它隐藏在数据库中,消耗着写入性能,却并未带来查询性能增益,及时发现这类索引并进行清理是十分必要的。此外,还有一些索引在一段时间内被高频使用,但随着业务的变动,这些索引不再被使用,但却一直被遗留下来,这也是烂索引。 对于上述情况,PolarDB-X提供了INFORMATION_SCHEMA.GLOBAL_INDEXES视图,用于查询表中全局索引被使用的情况,有了它,哪些全局索引在“磨洋工”,哪些全局索引“出工又出力”,一目了然。

20230816195359.jpg

低选择性索引

索引的选择性是指不重复的索引值的个数(也常被称为基数)和数据表的记录总数(#T)的比值,可由定义知道它的取值范围在 1/#T 到 1 之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以帮助数据库在查找时过滤掉更多无效的行。一个正面例子是主键索引,由于主键是不重复的,因此其选择性为最大值1,数据库利用主键查找数据时效率很高。一个反面的例子是,在性别、isDelete等属性上建索引。 如何发现这些低选择性的索引呢?最直接的办法是人工检查每个索引的真实含义,排除掉“性别”“Delete标志”之类含义的索引。此外对于全局索引,PolarDB-X支持用INFORMATION_SCHEMA.GLOBAL_INDEXES视图查看全局索引的基数和记录总数,我们可以根据这两个指标算出索引的选择性。

20230816195447.jpg

重复索引

重复索引是指在相同的列上按照相同的顺序创建了同类型的索引,Polardb-X不会禁止用户创建多个重复的索引。由于数据库在写入数据时,需要同步维护索引,因此多个重复的索引就需要数据库分别维护,此外优化器在优化查询语句时,也需要对这些重复索引逐个考虑,这会影响性能。 刻意引入重复索引的场景不常见,但不小心引入却是可能的。如下面的SQL是PolarDB-X中的单表,

20230816195530.jpg

用户可能想创建一个主键,然后为其加上unique限制,然后再加上索引以供其查询使用,实际上上述写法会创建出3个相互重复的索引,其实并不需要这么做。 一些索引从定义上来看是非重复索引,但从效果上来看,又是重复的。比如下面的建表语句,

20230816195617.jpg

一些用户可能会将查询SQL的where条件用到的列都建成索引,因此创建了索引 idx_id_name 。但是通常数据库在构建索引的时候,都会在索引的value属性中填入主键,以方便回表。因此索引 idx_name 的数据中是包含了主键 id 的,idx_name 和 idx_name_id效果相同。请避免构建这样的索引。

冗余索引

冗余索引和重复索引有所不同,如果创建了索引 (A, B),再创建索引 (A),后者就成了冗余索引。因为(A) 是 (A, B)的前缀索引,优化器使用索引时存在“最左匹配原则”,即会优先使用索引中的左侧列进行匹配,索引 (A, B) 是可以当做索引 (A) 来使用的。 冗余索引经常发生在为数据表添加新索引的时候,一些用户更倾向于添加新索引,而不是在现有索引上进行扩展。我们应当优先考虑在已有的索引上做扩展,而非随意添加新索引。如果确需添加新索引,也应当格外注意新引入的索引是否是一个冗余索引,又或者新索引是否会让旧有的索引变成冗余索引。当然,一味地扩展现有索引也不可取,可能会导致索引长度过长,从而影响其他使用该索引的SQL,这是一个trade off。 除了考虑“最左匹配原则”,我们还需注意unique约束。在有unique约束的情况下,一些看起来冗余的索引,实际上却并不冗余。

20230816195706.jpg

这里索引 idx_id_name 是无法完全替代索引 idx_id 的,因为索引 idx_id 除了方便按照id进行查找的作用外,还可以约束id不重复,而索引 idx_id_name 只能保证 (id, name) 不重复。

全局索引分区规则重复

像PolarDB-X这样的 Shared-Nothing 架构的分布式数据库一般会引入“分区”的概念,用户在建表时指定一个或若干个列为分区键,数据会在数据库内部按照分区键进行路由,从而将数据存储至不同的DN节点。如果一个查询语句的where条件中包含分区键,优化器就可以快速定位到一个具体分区并进行数据查找,但如果查询语句的where条件不含分区键,该查询就需要扫描全部分区,这有些类似于单机mysql的全表扫描,全分区扫描对于分布式数据库来说开销很大。 在实际数据库投入生产使用时,一个维度的分区往往不够灵活,将查询语句的where条件限制在必须包含“分区列”不够自由。分布式数据库一般会支持全局索引,它冗余了主表上的部分数据,并采用与主表不同的分区键,查询时首先根据全局索引的分区键定位到一个分区,然后从分区中查到主表的分区键和主键,最后回表得到完整数据。 全局索引让用户的查询语句不再受到“where条件必须包含主表分区列”的限制,且能避免全分区扫描的代价。从上文可知,用好全局索引的前提是设计良好的全局索引的分区方式,尤其是要避免全局索引和主表的分区方式重复,比如下面的表结构中,全局索引g_id和主表tb4的分区方式完全一致,g_id让系统付出了写入代价,却没有带来查询性能的增益。

20230816195750.jpg

全局索引分区大小不均匀

全局索引需要指定分区键,它的数据是按照分区规则存放于PolarDB-X的不同DN节点中的。设想,如果全局索引的分区规则设计的不够好,就会导致分区不均,一些DN节点存储大量数据,且承受大量的读写负载,而另一部分DN节点处于空闲状态。这造成了资源浪费,且会使数据库系统过早地到达性能瓶颈。 如下图,假设有一个业务系统建立了 seller_order 卖家订单信息表,该业务系统的特点是绝大部分订单来自于少数几个大卖家。我们只关注 seller_order 表上的全局索引 g_seller_id,它使用卖家的seller_id做分区键。我们假设有个大卖家的订单量占全部系统的一半,其在全局索引g_seller_id上的数据被路由到P5分区。可以看到P5分区会承受其它分区数倍的负载。

20230816195830.jpg

良好的全局索引应当保证数据尽可能均匀分布在不同分区。

全局索引中的range分区

在PolarDB-X中使用range分区作为全局索引的分区策略时应该额外注意,尽量避免将时间列作为分区列。

20230816195911.jpg

如上建表语句所示,全局索引 g_tm 使用了 tm 作为range分区的分区列,其默认值为当前时间。这里我们只考虑全局索引g_tm,其分区p5是一个catch-all 分区,在'2023-07-01 11:00:00'时间点以后,所有待插入的新数据都会被路由到p5分区(这是由新数据的tm列的值以及全局索引g_tm的路由规则决定的),因此p5分区会成为数据写入的瓶颈,p5分区所在DN上的数据量也将一直累积。 未来PolarDB-X将针对这一场景做出优化,但目前我们不推荐本例中的用法。

总结

我们先来回答一下文章开头提出的问题。warehouse表中有4个烂索引,分别是:重复索引idx_id(与主键重复)、重复索引idx_id_order_name(和主键效果一致)、冗余索引idx_order_id_order_name(索引idx_order_id_order_name_item_id可以代替它)、低选择性索引idx_deleted_order_id。 本文总结了一些常见的烂索引及其低效的原因,定期检查和清理这些烂索引,可以有效提升数据库的写入性能。可能有读者会问,表太多、索引太多,没精力挨个检查怎么办? 没关系,PolarDB-X最新推出inspect index功能,支持一键自动诊断烂索引,还能给出原因和整改建议,本文提到的烂索引都能识别。检查烂索引、优化数据库无需费时费力,欢迎试用,详情参见PolarDB-X索引诊断


相关实践学习
快速体验PolarDB开源数据库
本实验环境已内置PostgreSQL数据库以及PolarDB开源数据库:PolarDB PostgreSQL版和PolarDB分布式版,支持一键拉起使用,方便各位开发者学习使用。
相关文章
|
1月前
|
数据库 索引
深入探索数据库索引技术:回表与索引下推解析
【10月更文挑战第15天】在数据库查询优化的领域中,回表和索引下推是两个核心概念,它们对于提高查询性能至关重要。本文将详细解释这两个术语,并探讨它们在数据库操作中的作用和影响。
52 3
|
1月前
|
数据库 索引
深入理解数据库索引技术:回表与索引下推详解
【10月更文挑战第23天】 在数据库查询性能优化中,索引的使用是提升查询效率的关键。然而,并非所有的索引都能直接加速查询。本文将深入探讨两个重要的数据库索引技术:回表和索引下推,解释它们的概念、工作原理以及对性能的影响。
72 3
|
16天前
|
存储 缓存 数据库
数据库索引采用B+树不采用B树的原因?
B+树优化了数据存储和查询效率,数据仅存于叶子节点,便于区间查询和遍历,磁盘读写成本低,查询效率稳定,特别适合数据库索引及范围查询。
33 6
|
26天前
|
存储 缓存 数据库
数据库索引采用B+树不采用B树的原因
B+树相较于B树,在数据存储、磁盘读写、查询效率及范围查询方面更具优势。数据仅存于叶子节点,便于高效遍历和区间查询;内部节点不含数据,提高缓存命中率;查询路径固定,效率稳定;特别适合数据库索引使用。
28 1
|
1月前
|
数据库 索引
数据库索引
数据库索引 1、索引:建立在表一列或多列的辅助对象,目的是加快访问表的数据。 2、索引的优点: (1)、创建唯一性索引,可以确保数据的唯一性; (2)、大大加快数据检索速度; (3)、加速表与表之间的连接; (4)、在查询过程中,使用优化隐藏器,提高系统性能。 3、索引的缺点: (1)、创建和维护索引需要耗费时间,随数据量增加而增加; (2)、索引占用物理空间; (3)、对表的数据进行增删改时,索引需要动态维护,降低了数据的维护速度。
33 2
|
2月前
|
存储 关系型数据库 MySQL
Mysql(4)—数据库索引
数据库索引是用于提高数据检索效率的数据结构,类似于书籍中的索引。它允许用户快速找到数据,而无需扫描整个表。MySQL中的索引可以显著提升查询速度,使数据库操作更加高效。索引的发展经历了从无索引、简单索引到B-树、哈希索引、位图索引、全文索引等多个阶段。
71 3
Mysql(4)—数据库索引
|
1月前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
237 1
|
1月前
|
存储 关系型数据库 数据库
Postgres数据库BRIN索引介绍
BRIN索引是PostgreSQL提供的一种高效、轻量级的索引类型,特别适用于大规模、顺序数据的范围查询。通过存储数据块的摘要信息,BRIN索引在降低存储和维护成本的同时,提供了良好的查询性能。然而,其适用场景有限,不适合随机数据分布或频繁更新的场景。在选择索引类型时,需根据数据特性和查询需求进行权衡。希望本文对你理解和使用PostgreSQL的BRIN索引有所帮助。
47 0
|
1月前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第26天】数据库作为现代应用系统的核心组件,其性能优化至关重要。本文主要探讨MySQL的索引策略与查询性能调优。通过合理创建索引(如B-Tree、复合索引)和优化查询语句(如使用EXPLAIN、优化分页查询),可以显著提升数据库的响应速度和稳定性。实践中还需定期审查慢查询日志,持续优化性能。
93 0
|
2月前
|
关系型数据库 MySQL 数据库
深入浅出MySQL索引优化:提升数据库性能的关键
在这个数据驱动的时代,数据库性能的优劣直接关系到应用的响应速度和用户体验。MySQL作为广泛使用的数据库之一,其索引优化是提升查询性能的关键。本文将带你一探MySQL索引的内部机制,分析索引的类型及其适用场景,并通过实际案例演示如何诊断和优化索引,以实现数据库性能的飞跃。
下一篇
DataWorks