面向程序员的数据库访问性能优化法则特别说明: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种优化法则列举常用的优化手段并结合实例分析。二、Oracle数据库两个基本概念数据块(Block)数据块是数据库中数据在磁盘中存储的最小单位,也是一次IO访问的最小单位,一个数据块通常可以存储多条记录,数据块大小是DBA在创建数据库或表空间时指定,可指定为2K、4K、8K、16K或32K字节。下图是一个Oracle数据库典型的物理结构,一个数据库可以包括多个数据文件,一个数据文件内又包含多个数据块;ROWIDROWID是每条记录在数据库中的唯一标识,通过ROWID可以直接定位记录到对应的文件号及数据块位置。ROWID内容包括文件号、对像号、数据块号、记录槽号,如下图所示:三、数据库访问优化法则详解1、减少数据访问1.1、创建并使用正确的索引数据库索引的原理非常简单,但在复杂的表中真正能正确使用索引的人很少,即使是专业的DBA也不一定能完全做到最优。索引会大大增加表记录的DML(INSERT,UPDATE,DELETE)开销,正确的索引可以让性能提升100,1000倍以上,不合理的索引也可能会让性能下降100倍,因此在一个表中创建什么样的索引需要平衡各种业务需求。索引常见问题:索引有哪些种类?常见的索引有B-TREE索引、位图索引、全文索引,位图索引一般用于数据仓库应用,全文索引由于使用较少,这里不深入介绍。B-TREE索引包括很多扩展类型,如组合索引、反向索引、函数索引等等,以下是B-TREE索引的简单介绍:B-TREE索引也称为平衡树索引(BalanceTree),它是一种按字段排好序的树形目录结构,主要用于提升查询性能和唯一约束支持。B-TREE索引的内容包括根节点、分支节点、叶子节点。叶子节点内容:索引字段内容+表记录ROWID根节点,分支节点内容:当一个数据块中不能放下所有索引字段数据时,就会形成树形的根节点或分支节点,根节点与分支节点保存了索引树的顺序及各层级间的引用关系。一个普通的BTREE索引结构示意图如下所示:如果我们把一个表的内容认为是一本字典,那索引就相当于字典的目录,如下图所示:图中是一个字典按部首+笔划数的目录,相当于给字典建了一个按部首+笔划的组合索引。一个表中可以建多个索引,就如一本字典可以建多个目录一样(按拼音、笔划、部首等等)。一个索引也可以由多个字段组成,称为组合索引,如上图就是一个按部首+笔划的组合目录。SQL什么条件会使用索引?当字段上建有索引时,通常以下情况会使用索引:INDEX_COLUMN=?INDEX_COLUMN?INDEX_COLUMN=?INDEX_COLUMN?INDEX_COLUMN=?INDEX_COLUMNbetween?and?INDEX_COLUMNin(?,?,...,?)INDEX_COLUMNlike?||'%'(后导模糊查询)T1.INDEX_COLUMN=T2.COLUMN1(两个表通过索引字段关联)SQL什么条件不会使用索引?查询条件不能使用索引原因INDEX_COLUMN?不等于操作不能使用索引INDEX_COLUMNnotin(?,?,...,?)function(INDEX_COLUMN)=?INDEX_COLUMN+1=?INDEX_COLUMN||'a'=?经过普通运算或函数运算后的索引字段不能使用索引INDEX_COLUMNlike'%'||?INDEX_COLUMNlike'%'||?||'%'含前导模糊查询的Like语法不能使用索引INDEX_COLUMNisnullB-TREE索引里不保存字段为NULL值记录,因此ISNULL不能使用索引NUMBER_INDEX_COLUMN='12345'CHAR_INDEX_COLUMN=12345Oracle在做数值比较时需要将两边的数据转换成同一种数据类型,如果两边数据类型不同时会对字段值隐式转换,相当于加了一层函数处理,所以不能使用索引。a.INDEX_COLUMN=a.COLUMN_1给索引查询的值应是已知数据,不能是未知字段值。注:经过函数运算字段的字段要使用可以使用函数索引,这种需求建议与DBA沟通。有时候我们会使用多个字段的组合索引,如果查询条件中第一个字段不能使用索引,那整个查询也不能使用索引如:我们company表建了一个id+name的组合索引,以下SQL是不能使用索引的Select*fromcompanywherename=?Oracle9i后引入了一种indexskipscan的索引方式来解决类似的问题,但是通过indexskipscan提高性能的条件比较特殊,使用不好反而性能会更差。我们一般在什么字段上建索引?这是一个非常复杂的话题,需要对业务及数据充分分析后再能得出结果。主键及外键通常都要有索引,其它需要建索引的字段应满足以下条件: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压力比较大的系统,表的索引需要仔细评估必要性,另外索引也会占用一定的存储空间。1.2、只通过索引访问数据有些时候,我们只是访问表中的几个字段,并且字段内容较少,我们可以为这几个字段单独建立一个组合索引,这样就可以直接只通过访问索引就