特别说明:
1、 本文只是面对数据库应用开发的程序员,不适合专业 DBA , DBA 在数据库性能优化方面需要了解更多的知识;
2、 本文许多示例及概念是基于 Oracle 数据库描述,对于其它关系型数据库也可以参考,但许多观点不适合于 KV 数据库或内存数据库或者是基于 SSD 技术的数据库;
3、 本文未深入数据库优化中最核心的执行计划分析技术。
读者对像:
开发人员: 如果你是做数据库开发,那本文的内容非常适合,因为本文是从程序员的角度来谈数据库性能优化。
架构师: 如果你已经是数据库应用的架构师,那本文的知识你应该清楚 90% ,否则你可能是一个喜欢折腾的架构师。
DBA (数据库管理员): 大型数据库优化的知识非常复杂,本文只是从程序员的角度来谈性能优化, DBA 除了需要了解这些知识外,还需要深入数据库的内部体系架构来解决问题。
在网上有很多文章介绍数据库优化知识,但是大部份文章只是对某个一个方面进行说明,而对于我们程序员来说这种介绍并不能很好的掌握优化知识,因为很多介绍只是对一些特定的场景优化的,所以反而有时会产生误导或让程序员感觉不明白其中的奥妙而对数据库优化感觉很神秘。
很多程序员总是问如何学习数据库优化,有没有好的教材之类的问题。在书店也看到了许多数据库优化的专业书籍,但是感觉更多是面向 DBA 或者是 PL/SQL 开 发方面的知识,个人感觉不太适合普通程序员。而要想做到数据库优化的高手,不是花几周,几个月就能达到的,这并不是因为数据库优化有多高深,而是因为要做 好优化一方面需要有非常好的技术功底,对操作系统、存储硬件网络、数据库原理等方面有比较扎实的基础知识,另一方面是需要花大量时间对特定的数据库进行实 践测试与总结。
作为一个程序员,我们也许不清楚线上正式的服务器硬件配置,我们不可能像 DBA 那样专业的对数据库进行各种实践测试与总结,但我们都应该非常了解我们 SQL 的业务逻辑,我们清楚 SQL 中访问表及字段的数据情况,我们其实只关心我们的 SQL 是否能尽快返回结果。那程序员如何利用已知的知识进行数据库优化?如何能快速定位 SQL 性能问题并找到正确的优化方向?
面对这些问题,笔者总结了一些面向程序员的基本优化法则,本文将结合实例来坦述数据库开发的优化知识。
要正确的优化 SQL ,我们需要快速定位能性的瓶颈点,也就是说快速找到我们 SQL 主要的开销在哪里?而大多数情况性能最慢的设备会是瓶颈点,如下载时网络速度可能会是瓶颈点,本地复制文件时硬盘可能会是瓶颈点,为什么这些一般的工作我们能快速确认瓶颈点呢,因为我们对这些慢速设备的性能数据有一些基本的认识,如网络带宽是 2Mbps ,硬盘是每分钟 7200 转等等。因此,为了快速找到 SQL 的性能瓶颈点,我们也需要了解我们计算机系统的硬件基本性能指标,下图展示的当前主流计算机性能指标数据。
从图上可以看到基本上每种设备都有两个指标:
延时(响应时间):表示硬件的突发处理能力;
带宽(吞吐量):代表硬件持续处理能力。
从上图可以看出,计算机系统硬件性能从高到代依次为:
CPU —— Cache(L1-L2-L3) ——内存—— SSD 硬盘——网络——硬盘
由于 SSD 硬盘还处于快速发展阶段,所以本文的内容不涉及 SSD 相关应用系统。
根据数据库知识,我们可以列出每种硬件主要的工作内容:
CPU 及内存:缓存数据访问、比较、排序、事务检测、 SQL 解析、函数或逻辑运算;
网络:结果数据传输、 SQL 请求、远程数据库访问( dblink );
硬盘:数据访问、数据写入、日志记录、大数据量排序、大表连接。
根据当前计算机硬件的基本性能指标及其在数据库中主要操作内容,可以整理出如下图所示的性能基本优化法则:
这个优化法则归纳为 5 个层次:
1、 减少数据访问(减少磁盘访问)
2、 返回更少数据(减少网络传输或磁盘访问)
3、 减少交互次数(减少网络传输)
4、 减少服务器 CPU 开销(减少 CPU 及内存开销)
5、 利用更多资源(增加资源)
由于每一层优化法则都是解决其对应硬件的性能问题,所以带来的性能提升比例也不一样。传统数据库系统设计是也是尽可能对低速设备提供优化方法,因此针对低速设备问题的可优化手段也更多,优化成本也更低。我们任何一个 SQL 的性能优化都应该按这个规则由上到下来诊断问题并提出解决方案,而不应该首先想到的是增加资源解决问题。
以下是每个优化法则层级对应优化效果及成本经验参考:
优化法则 |
性能提升效果 |
优化成本 |
减少数据访问 |
1~1000 |
低 |
返回更少数据 |
1~100 |
低 |
减少交互次数 |
1~20 |
低 |
减少服务器 CPU 开销 |
1~5 |
低 |
利用更多资源 |
@~10 |
高 |
接下来,我们针对 5 种优化法则列举常用的优化手段并结合实例分析。
数据块是数据库中数据在磁盘中存储的最小单位,也是一次 IO 访问的最小单位,一个数据块通常可以存储多条记录,数据块大小是 DBA 在创建数据库或表空间时指定,可指定为 2K 、 4K 、 8K 、 16K 或 32K 字节。下图是一个 Oracle 数据库典型的物理结构,一个数据库可以包括多个数据文件,一个数据文件内又包含多个数据块;
ROWID 是每条记录在数据库中的唯一标识,通过 ROWID 可以直接定位记录到对应的文件号及数据块位置。 ROWID 内容包括文件号、对像号、数据块号、记录槽号,如下图所示:
数据库索引的原理非常简单,但在复杂的表中真正能正确使用索引的人很少,即使是专业的 DBA 也不一定能完全做到最优。
索引会大大增加表记录的 DML(INSERT,UPDATE,DELETE) 开销,正确的索引可以让性能提升 100 , 1000 倍以上,不合理的索引也可能会让性能下降 100 倍,因此在一个表中创建什么样的索引需要平衡各种业务需求。
索引常见问题:
索引有哪些种类?
常见的索引有 B-TREE 索引、位图索引、全文索引,位图索引一般用于数据仓库应用,全文索引由于使用较少,这里不深入介绍。 B-TREE 索引包括很多扩展类型,如组合索引、反向索引、函数索引等等,以下是 B-TREE 索引的简单介绍:
B-TREE 索引也称为平衡树索引 (Balance Tree) ,它是一种按字段排好序的树形目录结构,主要用于提升查询性能和唯一约束支持。 B-TREE 索引的内容包括根节点、分支节点、叶子节点。
叶子节点内容: 索引字段内容 + 表记录 ROWID
根节点,分支节点内容: 当一个数据块中不能放下所有索引字段数据时,就会形成树形的根节点或分支节点,根节点与分支节点保存了索引树的顺序及各层级间的引用关系。
一个普通的 BTREE 索引结构示意图如下所示:
如果我们把一个表的内容认为是一本字典,那索引就相当于字典的目录,如下图所示:
图中是一个字典按部首 + 笔划数的目录,相当于给字典建了一个按部首 + 笔划的组合索引。
一个表中可以建多个索引,就如一本字典可以建多个目录一样(按拼音、笔划、部首等等)。
一个索引也可以由多个字段组成,称为组合索引,如上图就是一个按部首 + 笔划的组合目录。
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 |
给索引查询的值应是已知数据,不能是未知字段值。 |
注: 经过函数运算字段的字段要使用可以使用函数索引,这种需求建议与 DBA 沟通。 有时候我们会使用多个字段的组合索引,如果查询条件中第一个字段不能使用索引,那整个查询也不能使用索引 如:我们 company 表建了一个 id+name 的组合索引,以下 SQL 是不能使用索引的 Select * from company where name=? Oracle9i 后引入了一种 index skip scan 的索引方式来解决类似的问题,但是通过 index skip scan 提高性能的条件比较特殊,使用不好反而性能会更差。
|
我们一般在什么字段上建索引?
这是一个非常复杂的话题,需要对业务及数据充分分析后再能得出结果。主键及外键通常都要有索引,其它需要建索引的字段应满足以下条件:
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 可以根据索引使用语法规则判断,复杂的 SQL 不好办,判断 SQL 的响应时间是一种策略,但是这会受到数据量、主机负载及缓存等因素的影响,有时数据全在缓存里,可能全表访问的时间比索引访问时间还少。要准确知道索引是否正确使用,需要到数据库中查看 SQL 真实的执行计划,这个话题比较复杂,详见 SQL 执行计划专题介绍。
索引对 DML(INSERT,UPDATE,DELETE) 附加的开销有多少?
这个没有固定的比例,与每个表记录的大小及索引字段大小密切相关,以下是一个普通表测试数据,仅供参考:
索引对于 Insert 性能降低 56%
索引对于 Update 性能降低 47%
索引对于 Delete 性能降低 29%
因此对于写 IO 压力比较大的系统,表的索引需要仔细评估必要性,另外索引也会占用一定的存储空间。
有些时候,我们只是访问表中的几个字段,并且字段内容较少,我们可以为这几个字段单独建立一个组合索引,这样就可以直接只通过访问索引就能得到数据,一般索引占用的磁盘空间比表小很多,所以这种方式可以大大减少磁盘 IO 开销。
如: select id,name from company where type='2';
如果这个 SQL 经常使用,我们可以在 type,id,name 上创建组合索引
font-family: Calibri; font-size: small;