如何高效使用索引10个索引优化最佳指南
如果您有SEO优化、网站建设需求请致电:18510193015
在数据库的世界里,索引究竟是什么呢?它其实是一种精心排好序的数据结构,一般存储在磁盘文件之中。索引会记录原数据的单个列或者多个列的信息。当进行查询操作时,程序就无需对所有记录逐一排查,而是先依据索引迅速定位到特定数据,再根据索引记录的指针位置,精准找到对应的原始数据记录。
打个比方,索引就如同书本的目录。我们想要查找某一章节的内容时,借助目录就能快速知晓对应的页数,直接翻到那一页即可。要是没有目录,那就只能从书本的开头一页一页地往后找,效率低下。
在关系型数据库里,常见的索引类型有 B 树索引、哈希索引以及全文索引等。其中,B 树索引应用最为广泛,适用于大多数查询场景。哈希索引在等值查询时表现出色,但对于范围查询和排序操作就有些力不从心了。全文索引则主要用于文本数据的搜索任务。
在 MySQL 中有两种数据访问方式,分别是顺序访问和索引访问。
顺序访问,也叫全表扫描。当执行查询时,它会从表的第一行开始,按照设定的条件依次匹配,一直到最后一行。倘若数据量较少,这种方式或许尚可接受。可一旦数据量庞大,其查询效率就会大打折扣。想象一下,前端请求一个数据,结果因为这种查询方式导致网站崩溃,这是多么糟糕的情况。
索引访问,正如其名,查询数据是在索引数据结构上展开的。索引数据事先已排好序,并且除了聚集索引外,通常不会保存完整的数据列。
不过,使用索引访问是有前提条件的。首先得创建好索引,而且检索的数据列必须存在于索引表中,只有满足这些条件,才能利用索引进行查询。需要注意的是,创建索引后,如果对数据进行更新操作,那么就需要同时更新和维护索引数据,这无疑会增加一定的开销。
索引的优点与缺点都十分明显。
优点方面:其一,能显著提升查询性能。索引可以大幅减少数据库需要扫描的数据行数,从而让查询速度得到质的飞跃。其二,有力支持排序和分组操作。索引能够帮助数据库快速对数据进行排序和分组,使得这些操作的性能得以提高。其三,可强制数据唯一性。通过创建唯一索引,能够有效确保数据的唯一性,防止重复数据的插入。
缺点方面:首先,会占用一定的存储空间。索引需要额外的空间来存储索引数据,这在存储空间有限的情况下可能会带来困扰。其次,会降低数据插入、更新和删除的性能。当对数据进行这些操作时,数据库需要同时维护索引,这就可能导致这些操作的执行速度变慢。最后,可能导致查询优化器选择错误的执行计划。如果索引使用不当,查询优化器可能会误判,选择了不合适的执行计划,进而导致性能下降。
索引失效也是一个需要关注的问题。所谓索引失效,就是表中某个字段虽然创建了索引,但由于 SQL 语句书写不合理,导致索引无法发挥作用。常见的索引失效场景有以下几种:查询条件包含 or 时,可能引发索引失效;like 通配符使用不当可能致使索引失效;对于联合索引,如果查询时的条件列不是联合索引中的第一个列,索引会失效;在索引列上使用 MySQL 的内置函数,索引会失效;对索引列进行运算,索引也会失效;还有当 mysql 估计使用全表扫描比使用索引更快时,就不会使用索引。例如,在 employees 表的 age 列上创建了索引,正确使用 age 列索引和使其失效的情况对比明显。像某些情况下,where 中的表达式看似等价于 age = 28,但 MySQL 无法自动解析,一旦对索引列进行运算,就无法正确找到对应的数据行,于是就会改为全表逐行扫描查询对比。
下面是 10 个索引优化的最佳指南。
1. 合理选择索引列。要挑选经常用于查询条件的列作为索引列。比如,如果经常依据用户的姓名来查询数据,那么就在用户表的姓名列上创建索引。同时,应将具有高选择性的列设为索引列。选择性指的是不重复的索引值和表数据的记录总数 T 的比值,范围在 1/T 到 1 之间。选择性越高,索引效果越好。例如,若一个列只有两个可能的值,创建索引的效果可能就不太理想。唯一索引的选择性为 1,这是最佳的索引选择性,性能也是最优的。此外,要避免在频繁更新的列上创建索引,因为这会降低数据更新的性能。对于 BLOB、TEXT 或很大的 VARCHAR 类型的列,在作为查询条件时,该列必须使用前缀索引,以此来提升查询性能,毕竟 MySQL 不允许索引这些列的完整长度。
2. 创建复合索引。在 SQL 优化时,有些人会在多个列上分别建立独立的单列索引,然而在多数情况下,这并不能提高 MySQL 的查询性能,这其实是一种错误的做法。MySQL 5.0 及之后版本引入了索引合并策略,这恰恰反映出表上索引创建得不合理,通常有以下几种原因:当出现对多个索引做相交操作时,往往意味着需要一个包含所有相关列的多列索引,而非多个独立的单列索引;当需要对多个索引做联合操作时,通常需要耗费大量的 CPU 和内存资源在算法的缓存、排序和合并操作上,特别是当其中有些索引的选择性不高,需要合并扫描返回的大量数据时。所以,复合索引作为多个独立单列索引的优化方案应运而生。复合索引由多个列构成,这些列的组合共同决定了索引的结构和作用范围。使用复合索引有以下几点注意事项:当多个列经常一起用于查询条件时,可以创建复合索引,这样能提高这些查询的性能。复合索引的列顺序至关重要,应将最具选择性的列放在最前面,然后依次排列其他列。例如,如果经常依据用户的姓名和年龄进行查询,那么就在用户表的姓名和年龄列上创建复合索引,并且把姓名列置于首位。如果在执行计划 EXPLAIN 中看到索引合并,就应当仔细检查查询和表的结构,看是否已经达到最优。也可以通过参数 optimizer_switch 来关闭索引合并功能,或者使用 IGNORE INDEX 提示让优化器忽略某些索引。创建复合索引的示例如下:在员工表的员工 id 和员工姓名列上创建复合索引 idx_employee_id_name,根据 employee_id 进行查询时,从执行计划中的 type 可以看出,索引是有效的。但要是根据 employee_name 进行查询,索引就会失效。
3. 避免过多的索引。过多的索引会占用大量的存储空间,并且会降低数据插入、更新和删除的性能。所以,只需创建必要的索引,依据实际的查询需求来抉择。
4. 选择合适的索引列顺序。正确的顺序取决于使用该索引的查询,并且要同时考虑如何更好地满足排序和分组的需求。有一个经验法则可供参考:将选择性最高的索引放在索引的最前列。在某些场景下,这个经验法则很有用,但通常不如避免随机 IO 和排序那么关键,考虑问题需要更全面。另外,具有随机或不可预测值的列不适合作为索引的前列。比如,一个包含随机生成的 UUID 的列,由于其选择性低且难以预测,不适合作为索引的首列。
5. 定期维护索引。随着数据不断地插入、更新和删除,索引可能会出现碎片化现象,从而影响查询性能。可以定期使用数据库的索引维护工具来优化索引。同时,要监控索引的使用情况,根据实际的查询需求调整索引。如果某些索引很少被使用,就可以考虑将其删除。
6. 善用覆盖索引。覆盖索引能够在不访问实际表数据的情况下,仅凭借索引就满足查询的需求。当数据库可以使用覆盖索引来满足查询时,就无需访问表中的数据行,从而显著提升查询性能。覆盖索引包含了查询所需的所有列的数据。当数据库执行查询时,如果可以使用覆盖索引,那么就不必从表中读取数据行,这样就能大大减少磁盘 I/O 操作,提高查询性能。例如,有一个员工表包含员工编号、姓名、年龄、部门等列。如果经常执行仅需员工编号和姓名列的查询,那么就在员工编号和姓名列上创建一个覆盖索引。当执行这类查询时,数据库可以直接从索引中获取所需的数据,无需访问表中的数据行。在没有覆盖索引的情况下,“不等于” 操作会导致索引失效。因为若使用索引,就需要依次遍历非聚簇索引 B + 树里所有叶节点,时间复杂度为 O (n),找到记录后还要回表,综合起来效率不如全表扫描,所以查询优化器会选择全表扫描。而在覆盖索引情况下,“不等于” 索引生效。因为查的两个字段被联合索引覆盖了,性能更高。虽然仍需依次遍历非聚簇索引 B + 树里所有叶节点,时间复杂度为 O (n),但无需回表了,整体效率比不使用索引要高,查询优化器就会采用索引。同理,没有覆盖索引时,左模糊查询会导致索引失效;有覆盖索引时,左模糊查询索引生效。主要原因是走非聚簇索引 B + 树遍历叶节点且不回表,效率高于全表扫描,查询优化器会选择效率高的方案。覆盖索引有利有弊。好处在于:一是可避免回表。Innodb 是以聚集索引的顺序来存储的,对于 Innodb 来说,二级索引在叶子节点中所保存的是行的主键信息,如果用二级索引查询数据,在查找到相应的键值后,还需通过主键进行二次查询才能获取真实所需的数据。而在覆盖索引中,二级索引的键值中就能获取所要的数据,避免了对主键的二次查询,减少了 IO 操作,提升了查询效率。二是能把随机 IO 变成顺序 IO 加快查询效率。由于覆盖索引是按键值的顺序存储的,对于 IO 密集型的范围查找来说,对比随机从磁盘读取每一行的数据 I0 要少得多,因此利用覆盖索引在访问时可将磁盘的随机读取的 IO 转变成索引查找的顺序 IO。由于覆盖索引能够减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一种常用的性能优化手段。弊端在于:索引字段的维护总归是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。这是业务 DBA,或者称为业务数据架构师的工作。并且不是所有类型的索引都能成为覆盖索引。覆盖索引必须要存储索引列,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以 MySQL 只能使用 B - Tree 所以来做覆盖索引,另外不同的存储引擎实现覆盖索引的方式也不同,而且不是所有的引擎都支持覆盖索引。
7. 避免在索引列上进行函数操作。当在索引列上进行函数操作时,数据库无法利用索引来加速查询。例如,如果在用户表的姓名列上创建了索引,并且查询中使用了 UPPER (name) 函数,那么数据库无法使用索引来加速查询。可以将函数操作移到查询的条件中,而不是在索引列上进行函数操作。比如,可以将查询条件改为 name = UPPER ('John'),而不是 UPPER (name) = 'JOHN'。
8. 聚簇索引。在拥有聚簇索引的表中,数据行实际上是按照索引键的顺序存储的。这意味着索引的叶子节点包含了实际的数据行。例如,在一个存储学生信息的表中,如果按照学生的学号建立聚簇索引,那么数据行将按照学号的顺序在磁盘上存储。在 InnoDB 存储引擎中,若表有主键,则主键自动作为聚簇索引;若没有主键,会选择第一个唯一索引作为聚簇索引;若没有唯一索引,会隐式定义一个包含所有列的隐藏列作为聚簇索引。聚簇索引有其优点:一是快速范围查询。由于数据是按照索引键的顺序存储的,对于范围查询极为高效。数据库可以快速定位到范围的起始位置,然后顺序读取数据,无需进行大量的随机磁盘访问。二是高效的连接操作。如果多个表通过具有聚簇索引的列进行连接操作,数据库可以更有效地合并数据,因为数据已经按照连接列的顺序存储。三是减少磁盘 I/O。因为数据的存储与索引的结构紧密结合,对于某些查询,可以减少磁盘 I/O 操作,提高查询性能。当然,聚簇索引也有缺点:一是插入和更新成本高。当插入或更新数据行时,数据库可能需要移动大量的数据以保持数据的有序性。这会导致较高的插入和更新成本,特别是在频繁进行插入和更新操作的情况下。二是索引占用空间大。聚簇索引通常需要占用较多的存储空间,因为它包含了实际的数据行。这可能会对存储资源造成压力。三是不适合频繁更改索引键值的场景。如果索引键的值经常改变,数据库需要不断地调整数据的存储顺序,这会导致性能下降。不同的数据库管理系统对于聚簇索引的创建方式略有不同。一般来说,可以在创建表时指定一个列作为聚簇索引键。在选择聚簇索引键时,需要考虑以下因素:列的唯一性。如果选择的列具有较高的唯一性,那么聚簇索引的效果会更好,因为可以减少数据的重复存储和冲突。查询模式。根据常见的查询模式选择聚簇索引键。如果经常进行范围查询或连接操作,可以选择与这些查询相关的列作为聚簇索引键。数据的稳定性。选择一个相对稳定的列作为聚簇索引键,避免频繁更改索引键值。
9. 冗余、重复索引。重复索引,就是在相同列上按照相同的顺序创建的相同类型的索引。这种情况应当避免,一旦发现就要立即移除。比如,创建一个主键,先加上唯一限制 (unique (id)),然后再加上索引 (index (id)) 以供查询使用。然而唯一限制和主键限制都是通过索引使用,因此,上述写法实际上在相同的列上创建了三个重复的索引。通常并没有理由要这样做,除非是在同一列上创建不同类型的索引来满足不同的查询需求。冗余索引和重复索引有一些区别,例如:如果创建了索引,再创建那就是冗余索引,因为 A 就是前一个索引的前缀索引。索引完全可以当作 A 来使用。但是如果创建了索引 B 那就不是冗余索引了。因为 B 不是索引 (A,B)的最左前缀索引。另外,其他不同类型的索引,例如哈希,全文索引也不会是 B - Tree 的冗余索引。
10. 使用索引提示。当查询优化器选择了错误的执行计划时,可以使用索引提示来强制数据库使用特定的索引。不过,索引提示应当谨慎使用,只有在确定查询优化器选择了错误的执行计划时才使用。过多的索引提示可能会导致数据库无法选择最优的执行计划。先在账户表的姓名列上创建索引,然后强制数据库使用特定的索引。
总结:通过上述的阐述,旨在说明如何高效运用索引并给出了对应的 10 个索引优化的最佳实践。索引是提升数据库查询性能的关键工具,但如果使用不当,可能会造成性能下滑。遵循上述 10 个指南,在一定程度上能够高效地使用索引,提高数据库的性能。当然,索引优化还有其他出色的方案,需要在实际数据库开发过程中持续学习和实践。应当依据具体的查询需求和数据特点来挑选合适的索引优化方法,以实现最佳的性能效果。