数据库性能优化——索引
一、引入
说到查询性能的优化,我们首先会想到对数据表通过建立索引。这也是最基础的优化方式,它能根据查询要求,迅速缩小查询范围,减少数据的访问次数,从而可以优化数据库性能,避免全表扫描。
二、简单的实例
先来看一个有无创建索引来进行查找的简单实例,比较下两者的查询效率,能具象地了解索引对查询效率的提高程度。
SQL语句:
-- 判断是否存在表,若存在则删除
USE Test2
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'objects')
DROP TABLE objects -- 删除表
-- 判断是否存在索引,若存在则删除
USE Test2
IF EXISTS(SELECT name FROM sys.indexes
WHERE name = 'object_id_ind')
DROP INDEX objects.object_id_ind -- 删除索引
-- 创建表
CREATE TABLE objects
(
owner varchar(30),
object_name varchar(50),
object_id int,
createdtime datetime,
status varchar(10)
);
--数据从本地文件objects.csv (数据总行数: 9588)导入到表中。
--无索引,直接查询,并查看其查询计划
SELECT * FROM objects WHERE object_id=1477;
-- 创建索引,再进行查询,并查看其查询计划
SET NOCOUNT OFF
CREATE UNIQUE CLUSTERED INDEXobject_id_ind ON objects (object_id);
SELECT * FROM objects WHERE object_id=1477;
操作:
1.在SQL ServerManagement Studio中,点击“查询”→“显示估计的查询计划”,会出现如下图所示的执行计划图:
2.点击表扫描,即可查看该操作的所需开销。
3.同样,创建索引后,也可查看其执行计划。
4.在有无索引的情况下的执行计划,如下图所示:
小结:
从这个实例可以看出,把查询语句中where子句中的字段设置为索引时,查询效率有所提高。但由于数据量的缘故,效果不是太明显。
当然,一个表中可以建多个索引,如一本字典可以建多个目录一样(按拼音、笔划、部首等等)。一个索引也可以由多个字段组成,称为组合索引,如按部首+笔划的组合目录。这里不再具体展示。
三、设计索引的重要性
数据库索引的原理非常简单,但在复杂的表中真正能正确使用索引的人很少。因为天下没有免费的午餐,使用索引是需要付出代价的。
在设计索引时,我们需要考虑两个重要的因素:
1) 对某个属性使用索引能极大地提高对该属性上的值的检索效率,使用到该属性时,还可加快表的连接。
2) 对表上某个属性的索引会使得对表的数据插入、删除和修改变得复杂和费时,即大大增加表记录的DML(INSERT,UPDATE,DELETE)开销。
创建并使用正确的索引,可以减少数据的访问次数,其性能可提升100、1000倍以上。但是,不合理的索引也可能会让性能下降100倍。
因此在一个表中创建什么样的索引显得尤为重要,同时还需要平衡各种业务需求。
四、索引建立的字段
知道了索引的重要性,那一般在哪些字段需要建立索引呢?这是一个非常复杂的话题,需要对业务及数据充分分析后再能得出结果。这边可以提供给大家普遍的经验分享。
索引建立的字段:
主键和外键字段通常要建索引。注:SQL Server会为主键自动添加了聚集索引。
如果其它字段需要建索引,应满足以下条件:
1、字段出现在查询条件中,并且查询条件可以使用索引;
2、语句执行频率高,一天会有几千次以上;
3、通过字段条件可筛选的记录集很小,数据筛选比例需要根据表数据量来评估。
以下是牛人的经验公式,可用于快速评估:
小表(记录数小于10000行的表):筛选比例<10%;
大表:(筛选返回记录数)<(表总记录数*单条记录长度)/10000/16
单条记录长度≈字段平均内容长度之和+字段数*2
以下是一些字段是否需要建B-TREE索引的分类:
字段类型 | 常见字段名 | |
需要建索引的字段 | 主键 | ID,PK |
外键 | PRODUCT_ID,COMPANY_ID,MEMBER_ID,ORDER_ID,TRADE_ID,PAY_ID | |
有对像或身份标识意义字段 | HASH_CODE,USERNAME,IDCARD_NO,EMAIL,TEL_NO,IM_NO | |
索引慎用字段,需要进行数据分布及使用场景详细评估 | 日期 | GMT_CREATE,GMT_MODIFIED |
年月 | YEAR,MONTH | |
状态标志 | PRODUCT_STATUS,ORDER_STATUS,IS_DELETE,VIP_FLAG | |
类型 | ORDER_TYPE,IMAGE_TYPE,GENDER,CURRENCY_TYPE | |
区域 | COUNTRY,PROVINCE,CITY | |
操作人员 | CREATOR,AUDITOR | |
数值 | LEVEL,AMOUNT,SCORE | |
长字符 | ADDRESS,COMPANY_NAME,SUMMARY,SUBJECT | |
不适合建索引的字段 | 描述备注 | DESCRIPTION,REMARK,MEMO,DETAIL |
大字段 | FILE_CONTENT,EMAIL_CONTENT |
五、SQL使用索引的场景
除了需要知道在哪些字段建立索引,我们还需要知道使用索引和不能使用索引的运算。
SQL使用索引的条件:
当字段上建有索引时,通常以下操作会使用索引:
INDEX_COLUMN = ?
INDEX_COLUMN > ?
INDEX_COLUMN >= ?
INDEX_COLUMN < ?
INDEX_COLUMN <= ?
INDEX_COLUMN between ? and ?
INDEX_COLUMN in (?,?,...,?)
INDEX_COLUMN like ?||'%'(后导模糊查询)
T1. INDEX_COLUMN=T2. COLUMN1(两个表通过索引字段关联)
SQL不能使用索引的条件:
查询条件 | 不能使用索引原因 |
INDEX_COLUMN <> ? INDEX_COLUMN not in (?,?,...,?) | 不等于操作不能使用索引,查找的记录太多 |
function(INDEX_COLUMN) = ? INDEX_COLUMN + 1 = ? INDEX_COLUMN || 'a' = ? | 经过普通运算或函数运算后的索引字段不能使用索引 |
INDEX_COLUMN like '%'||? INDEX_COLUMN like '%'||?||'%' | 含前导模糊查询的Like语法不能使用索引 |
INDEX_COLUMN is null | B-TREE索引里不保存字段为NULL值记录,因此IS NULL不能使用索引 |
NUMBER_INDEX_COLUMN='12345' CHAR_INDEX_COLUMN=12345 | Oracle在做数值比较时需要将两边的数据转换成同一种数据类型,如果两边数据类型不同时会对字段值隐式转换,相当于加了一层函数处理,所以不能使用索引。 |
a.INDEX_COLUMN=a.COLUMN_1 | 给索引查询的值应是已知数据,不能是未知字段值。 |
注:
1.经过函数运算字段的字段要使用可以使用函数索引,这种需求建议与DBA沟通。
2.有时候我们会使用多个字段的组合索引,如果查询条件中第一个字段不能使用索引,那整个查询也不能使用索引。
如:我们company表建了一个id+name的组合索引,以下SQL是不能使用索引的:
Select * from company where name=?
因为优化器会认为需要一行行的扫描会更有效,会选择TABLE ACCESS FULL。但是如果换成了
Select name from company where name=?
优化器会直接去索引中找到name的值,因为从B树中就可以找到相应的值。
六、判断索引的正确性
简单SQL可以根据索引使用语法规则判断。复杂的SQL,可以判断SQL的响应时间,但是这会受到数据量、主机负载及缓存等因素的影响,有时数据全在缓存里,可能全表访问的时间比索引访问时间还少。要准确知道索引是否正确使用,需要到数据库中查看SQL真实的执行计划。
七、索引对DML附加的开销
索引对DML(INSERT,UPDATE,DELETE)附加的开销没有固定的比例,与每个表记录的大小及索引字段大小密切相关,以下是一个普通表测试数据,仅供参考:
索引对于Insert性能降低56%
索引对于Update性能降低47%
索引对于Delete性能降低29%
因此对于写IO压力比较大的系统,表的索引需要仔细评估必要性,另外索引也会占用一定的存储空间。
八、后记
该文章主要说明了索引的重要性,以及在什么地方建立索引。但逻辑还存在不足,SQL Server 和 Oracle 都有所涉及,但两者具通用性。内容还不够完善,关于聚簇索引和非聚簇索引的内容这边没有细讲。
最后,希望能与大家分享。
[参考文献]
1. 面向程序员的数据库访问性能优化法则
2. 在SQL Server中使用索引的技巧
http://windows.chinaitlab.com/sql/916577.html