文章目录
为什么存在性能问题?
为什么是 B-树索引?
优化查询条件
等值查找
复合条件
范围查找
LIKE 运算符
使用函数
空值查找
多表连接查询
排序和分组
DML
总结
SQL 语句优化是数据库优化的重要内容;无论开发人员还是 DBA,在工作中都不可避免需要解决一些 SQL 性能问题。
SQL 优化的方法有多种,其中最有效、最常用的方法就是索引(Indexing)。因此,我们就来详细讨论一下索引(B-树)的原理,以及如何利用索引编写高性能的 SQL 语句。
本文内容适用于各种数据库,包括但不限于 MySQL、Oracle、SQL Server 、PostgreSQL 以及 SQLite。
为什么存在性能问题?
由于 SQL 是一种关注结果(what)的语言,具体过程(how)由数据库系统来实现,这就往往导致我们容易编写出低性能的 SQL 语句。我们先来看看数据库为什么存在性能问题。
目前,数据库管理系统主要还是使用磁盘作为数据的存储介质;因为磁盘的访问性能虽然比内存慢几个数量级,但是具有价格便宜、大容量和断电不丢失的特性。对于交易类型的数据库(OLTP),一般都是小型的事务,操作很少的随机数据,但是并发很高。这种 IO 密集型数据库操作使用磁盘存储数据文件时存在两个问题:
直接查找数据时,每次都需要扫描表中的全部数据;
随着数据量的增长,扫描磁盘数据的性能急剧下降。
下面是一个简单的查询语句,查找 emp_id 等于 5 的员工(数据来源):
select *
from employee
where emp_name = '张三';
那么数据库如何找到我们需要的数据呢?如果没有索引,那就只能扫描整个 employee 表,然后依次判断 emp_name 字段是否等于“张三”并返回满足条件的数据。这种方式一个最大的问题就是当数据量逐渐增加时,全表扫描的性能也就随之明显下降。
因此,数据库的性能优化首先就是需要减少磁盘的访问;其中主要的方法就是利用索引技术。
📝另一个减少磁盘 IO 的常用方法就是利用内存进行数据缓存,在数据库中称为缓冲区(Buffer Pool、Buffer Cache)。
那么为什么索引能够提高查询的性能?为什么几乎所有的数据库默认使用 B-树索引(B+树、B*树)?这就需要先来了解一下 B-树。
为什么是 B-树索引?
为了解决查询的性能问题,数据库引入了一个新的数据结构:索引。索引就像书籍后面的关键字索引,按照关键字进行排序,并且提供了指向具体内容的页码。如果我们在 id 字段上创建了索引(例如 B-树索引),数据库查找的过程大概如下图所示:
B-树(平衡树、Balanced Tree)索引就像是一棵倒立的树,其中的节点按照顺序进行组织;节点左侧的数据都小于该节点的值,节点右侧的数据都大于节点的值。
数据库查找时,首先通过索引找到工号为 5 的节点,再通过该节点上的指针(通常是数据的物理地址)访问数据所在的磁盘位置。举例来说,假设每个索引分支节点可以存储 100 个记录,100 万(1003)条记录只需要 3 层 B-树即可完成索引。通过索引查找数据时需要读取 3 次索引数据(每次磁盘 IO 读取整个分支节点),加上 1 次磁盘 IO 读取数据即可得到查询结果。
相反,如果采用全表扫描,需要执行的磁盘 IO 可能高出几个数量级。当数据量增加到 1 亿时,B-树索引只需要再增加 1 次索引 IO 即可;而全表扫描则需要再增加几个数量级的 IO。
📝全表扫描并不一定比索引查找更慢,当表中的记录较少,或者查询需要返回表中大部分的数据时,直接通过全表扫描可能更快。
不仅如此,数据库在实现上还对 B-树索引进行了改进,在索引节点之间增加互相连接的指针(B+树、B*树),能够提供更好的范围查询性能。例如,以下是 Oracle 中的 B*树索引示意图: