数据库性能优化1

概述

  引子

  数据性能指标

  数据性能优化误区

  没有条理的一些优化技巧

    影响数据库性能的常见原因

  数据库结构的设计

  事务和隔离级别

  T-SQL语句的编写

  硬件资源

基础理论:物理运算符

 

1,引子

  数据库性能的衡量不仅仅是一个"快"字,还有数据库性能的稳定,数据库性能不能断崖式的下降

  如,200个请求时响应时间200ms,1000个请求时响应时间下降到500ms是合理;如果210个请求响应时间600ms,那就是断崖式下降

  数据库响应慢的综合表现:

    没有达到最大吞吐量:是否是硬件资源不足

 合理的预期表现:数据量越大,响应时间是不可避免的越长

   资源消耗过多,运行速度的下降超过预期

 

2,数据性能指标

1,响应时间:最小化每个Sql的执行时间,合理增加吞吐量,网络IO

2,吞吐量

3,可扩展性:简单的增加资源就可以解决环境的能力

 

3,数据性能优化误区

误区一:加硬件能缓解性能问题

误区二:只要数据库性能好就没问题(把应用层实现的业务的问题丢到数据库去实现,想通过优化数据库来解决问题)

误区三:数据库任何性能问题都可以通过索引解决(应该先检查日志,确认性能问题所在,再考虑优化sql,最后考虑索引,索引也可能会引起一些sql变慢)

误区四:只使用存储过程或者不允许使用存储过程,sql的激进的编码方式

误区五:查询性能代表数据库性能

误区六:性能优化不是追求问题的消失(这是无法实现的),而是寻求系统的平衡,资源利用优化,

 

4,没有条理的也可能有错误的一些点

一个批量Sql请求大部分情况下不要拆开成多个小请求

优化的技巧不是和某个系统绑定的,没有一个人能精通所有的数据库,精通Mysql,SqlServer,Oracle任意一个,副修其中一个

避免全表扫描出现的情况
  not null
  like:加通配符
  is null
  <>,!=
  A or B:A或B任意一个无索引时,都会全表扫描
  count:不带任何条件的count
  update 不要更新全表字段
  不要在索引列上计算
  组合索引,排序时应按照组合索引中各列的顺序进行排序,即使索引中只有一个列是要排序的,否则排序性能会比较差

1,先分页再join
2,避免全表扫描
3,索引优化
4,分库分表
5,减少sql请求
6,禁止无条件的查询语句
7,避免在SELECT子句中使用DISTINCT. 一般可以考虑用EXIST替换, EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果
8,不要在索引列上计算
9,用>=替代> 
10,指定返回列替代*
11,检查死锁:sp_lock
12,withnolock

 

5,影响SqlServer的常见因素:(大部分影响数据库性能都是设计和编码引起的)

  数据库结构的设计

  事务和隔离级别

  T-SQL语句的编写

  硬件资源

 

6,数据库结构的设计

  对性能的整体影响非常大,一个糟糕的数据库结构设计,将会导致你无论如何优化都无法改变这种设计带来的性能瓶颈,除非你改变数据库的结构设计

  糟糕的数据库结构设计可以导致你的sql无法进行优化,无法改写,或者高昂的改写成本

  糟糕的数据库结构设计可以导致你的sql优化要付出高昂的代价

  糟糕的数据库结构设计可以导致你的索引效率低,甚至索引失效

  示例:电商平台的订单:订单信息与订单商品应该拆开成两个表,而不是在一个表中存储

  1,了解你的业务,才能做好优化

  2,优先考虑第三范式的设计

  3,字段冗余:字段冗余会带来查询的优化,也会带来维护的成本;应当权衡维护的成本和查询的成本

    最常见的用户名称的冗余:你有没有想过为什么大部分网络游戏都不支持你在游戏中修改你的游戏的角色名

  4,表关联尽可能的少:严厉控制那些新手们写的关联查询,在设计表结构时,尽可能考虑表的增删改查使用最简单的sql语句,

    在设计表结构时,预见表的增删改查是否需要关联查询操作,这些关联查询的方案是否可以优化,或者以简单的sql语句来替换,避免过多的表关联

  5,坚持最小原则,尽可能使用最小长度的数据库类型字段来存储数据,尤其是大型表结构

  6,考虑表的归档难度:修改时间,删除时间,创建时间,创建人,修改人,删除人,状态标识用于归档,且控制这些字段的长度

  7,在适当的地方使用约束:这点主要是从安全方面来考虑,不能因为追求性能而放弃安全,这里的安全包括数据类型的安全,数据范围的安全

 

7,事务和隔离级别

  1,确保每一个事务都是最小化

  2,事务的异常都能被捕捉

  3,事务能够输出执行日志

  4,某些隔离级别会将事务持续到执行结束后才会释放锁,默认的事务隔离级别可以满足大部分要求

 

9,T-SQL语句的编写

  1,只查询需要的字段,不返回所有字段

  2,使用简单Sql语句:

简单的关联2-4个表,其中有大数据量表只有一个

没有复杂的过滤条件,只有2-3个判断,并且有一个过滤条件可以明确使用索引

简单语句可以提高查询速度,也方便维护与开发,同时复杂的查询语句可能会增加查询优化器分析复杂度,甚至不能选择最有的查询计划;

越复杂的语句在大数据量的查询中,执行计划发生异常的几率会增加

  3,TempDB,临时表,是全局的;推荐使用独立的磁盘驱动器,减少分配资源时的抢占;

TempDB在系统每次重启的时候,都会按照Log系统库来重建;

为了减少TempDb的资源争用,一般会对TempDB的数据文件进行拆分,拆分的个数一般和cpu的核数保持一致

  4,用户数据库和日志文件隔离存放

    用户数据文件是随机读写;多个数据文件,可以分开放在不同的驱动器上,可以实现数据文件的并行读写,提高IO

    日志文件是顺序读写的;多个日志文件,只能写完一个再写下一个,所以

10,硬件资源(省略)

 

11,基础理论:物理运算符 

  数据库执行计划分析

  T-SQL语句的执行过程:语法分析-->绑定-->优化--->执行-->返回结果

    1,语法分析,编译分析校验sql语句,转换成sql server可以识别的结构,这个结构就是逻辑查询树

    2,绑定,把结构(查询逻辑树)与数据库中的对象绑定,这里的对象指的是表,存储过程,函数等,加载相应的元数据

    3,优化,将绑定后的查询树交给查询优化器,预估执行计划,优化器只会尽可能的去查找最佳方案;这也就意味着优化器可能会选择不理想的执行方案

      链接表的越多,查询方案也就越多,要寻找最优的方案也就越加困难

  SqlServer Management Studio的预估执行计划

    SqlServer的查询优化器不能保证查询是最好的,也不能保证查询是最快的

    预估执行计划会显示每一条查询语句的开销比例

    如下图所示的是一个针对用户表的查询;在查询中第一步执行针对UserInfo表的聚集索引的Scan(扫描)操作

    下图显示了执行Scan操作构成中的IO/CPU等资源消耗指标

      重点关注:IO的开销;预估的函数;Cpu开销;Number Of Executeion(执行次数,在连接操作中,一个扫描操作可能执行多次)

      开销大小:开销比例

数据库性能优化1

 

预估执行计划明细 

数据库性能优化1

查询优化器的优化过程

    1,简化阶段:

      检查当前语句在内存中是否有执行缓存,如果有,会直接使用已有的执行计划

      如果没有执行计划缓存,简化阶段会移除一些没用的执行条件,如where 1=1

    2,普通计划搜索:

      判断当前语句是不是只可能产生一个执行计划或者明显的有一个最优查询计划;这就是一般查询计划,

      如下图优化节点右键的【属性】视图,在预估执行计划的优化级别(OptimizationLevel)参数中,你就可以看到执行计划是否为一般优化(TRIVIAL)

      数据库性能优化1  

    3,如果找到普通计划,则更新普通计划搜索的统计信息

    4,基于开销的优化:

      未找到一般执行计划时,开始执行基于CPU阶段的优化

        阶段0-针对sql分析出一系列的执行计划

        阶段1-快速计划,使用优化规则生成可执行的计划,比较执行计划的成本;找到成本优化的计划;如果没有找到最优计划;则判断是否可以并行操作,

          如果可以,则生成并行计划与串行计划进行比较;使用最低成本的计划

        阶段2-完全优化,选择最低成本的优化

    5,生成执行计划

  执行计划中的物理运算符:

    1,阻塞运算符与非阻塞运算符

      阻塞运算符:需要等待所有的子运算完成才能返回结果,典型的Sort,Sort是针对所有返回集合上的排序;这种运算符开销大

      非阻塞运算符:每一次处理结果都需要处理,存储的数据比较少,开销比较小,典型的Top/Limit

    2,逻辑运算符:在优化阶段使用的是逻辑运算符

      如inner join ,left join;在优化阶段使用的是逻辑运算符,而在执行阶段会转换成物理运算符 ;如inner join在优化过程中会转换成嵌套的循环操作

    3,物理运算符:执行阶段会转换成物理运算符

      数据访问运算符:

        扫描运算符>

          表扫描(Table Scan如对一个没有任何索引的表进行没有任何条件的简单查询会发生,或者是对一个数据量很小且没有创建聚集索引时)

          示例1:Department表,总数据量200行,只有索引:CREATE NONCLUSTERED INDEX idx_nc_dep ON Department(DepartmentNo,Name) 

         数据库性能优化1

          示例2:Department表,总数据量200行,只有索引:CREATE NONCLUSTERED INDEX idx_nc_dep ON Department(DepartmentNo,Name) 

          数据库性能优化1

          示例3:Department2表,总数据量100064行,无任何索引

          数据库性能优化1        

          聚集索引扫描(Clustered Index Scan如对一个有主键索引的表进行没有任何条件的简单查询)

          示例:UserInfo表,总数据量10万行,聚集索引Id,非聚集索引UserCode

          数据库性能优化1

          非聚集索引扫描(Non Clustered Index Scan如对一个仅有非聚集索引的表进行没有任何条件的简单查询会发生);大数据量的扫描会导致内存清理缓存

          示例Department2,总数据量100064行,非聚集索引:CREATE NONCLUSTERED INDEX idx_nc_dep ON Department2(DepartmentNo,Name) 

          示例Department4,总数据量464行,非聚集索引:CREATE NONCLUSTERED INDEX idx_nc_dep4 ON Department4(DepartmentNo) 

          数据库性能优化1

        查找运算符>

          聚集索引查找:Clustered Index Seek;如对一个有聚集索引的表进行在主键索引字段上过滤(where)时就会发生这种情况

          如Department3表,总数据量464行,Id为主键 :select top 1000 * from Department where id=‘dd‘

          如UserInfpp表,总数据量10万行,Id为主键,非聚集索引UserCode :select  Id,UserCode,UserName from UserInfo where Id=‘11‘

          数据库性能优化1

          非聚集索引查找:Non Clustered Index Seek;如查询非聚集索引字段,并在非聚集索引字段上过滤(where)时就会发生这种情况

          如Department2表,总数据量10万行,非索引idx_nc_dep ON Department2(DepartmentNo,Name) 

          如UserInfo表,总数据量10万行,非索引UserCode;聚集索引Id

          数据库性能优化1 

        Key Lookup,Rid LookUp>

          如果对返回的列在查询条件上若建立了非聚集索引,此时将可能尝试使用非聚集索引查找,

          如果返回的列没有创建非聚集索引,此时会返回到数据页中去获取这些列的数据,即使表中存在聚集索引或者没有,都会返回到表中或者聚集索引中去获取数据;此时按照不同的情况会发生以下三种查找            

          此时如果表【有创建聚集索引】则称为:标签查找Key Lookup,也叫键查找;

          示例UserInfo表,总数据量10万行,非索引UserCode;聚集索引Id

          数据库性能优化1

          此时如果表中【没有聚集索引】但是【存在非聚集索引】我们称为:行ID查找RID Lookup

          示例如Department2表,总数据量10万行,非索引idx_nc_dep ON Department2(DepartmentNo,Name) 

          数据库性能优化1      

          KeyLookup,RIDLookup查找是很耗费性能,当出现这种情况;

            一般都是缺失了索引;

            或者索引没有正确覆盖到我们的Sql语句

            或者索引字段上使用了函数

 

      关联运算符和聚集运算符:

        关联运算符>

          Join之嵌套循环(Nested Loop):对于外部输入的每一行,都会扫描内部输入,然后匹配行,这个过程不创建内外的数据结构,

            这个过程中,不会使用Tempdb;适合小数据量输入表为外部表,以较大的数据量的集合为内部表

            正确使用方式:小数据量表做外表,且有索引(聚集索引或非聚集索引);大数据量表为内部表,且有索引(聚集索引或非聚集索引)

              注意:从语句是无法从表的先后,或者返回的字段来判断外部表,内部表的;而是根据数据量(执行计划会自动判断)

          下图:外部表Department3表,数据464总行数;主键Id;内部表Company,数据10万行,主键Id,非聚集索引CompanyNo;

          我们可以看到在嵌套循环中的内部查询时,使用的是主键索引扫描,执行次数464次;如果这这464次是表扫描,那开销将会更大

            数据库性能优化1

        Join之合并连接:从两个数据表(都要排序)中各取一个值进行比较,如果条件匹配,就把两行连接起来返回,如果不等,就把小的值去掉,按顺序去下一个值,一直到其中的一边遍历完成

          算法的复杂度:取绝于最大表的数据量

          对大表的操作要更高效一些;因为merge join需要的排序,所以消耗的资源更多。 大部分情况下使用merge join的地方,hash join都可以发挥更好的性能,即散列连接的效果都比排序合并连接要好。

          但是如果行已经被排过序,连接时不需要再排序,这时排序合并连接的性能会优于散列连接。

          适用情况:

1.有序主键索引

2.不等价关联(>,<,>=,<=,<>)

3.HASH_JOIN_ENABLED禁用

4. 用在没有索引,并且数据已经排序的情况

          示例:StoreUsers表,主键索引Age,另外Id,UserId相等;

             StoreUsers2表,主键索引Age,另外Id,UserId相等

             第二个语句我们使用的是没有排序的连接字段,本应该使用Hash连接,在使用MERGE强制合并连接时,执行计划内部对其进行了排序;

             你同时可以发现它占了89%的开销,而不使用强制合并的哈希连接反而只有8%开销

数据库性能优化1

        Join之哈希连接:会使用来自顶部输入的每一行生成哈希表,使用底部输入的每一行探测这个哈希表,然后输出匹配的行;

          对于输入的两个运算符都只有一次操作;哈希连接是不需要记录排序的;哈希连接是最消耗性能的

          

          如下图:执行器先对StortUser2表进行遍历,每行进行哈希计算,将哈希值保存在哈希表中;然后将StoreUser作为检索输入,使用相同的哈希算法进行计算并匹配;

            在哈希算法的过程中是要预估所需要的内存的;如果无法获取足够内存,则会对哈希表进行拆分,一部分在内存中,一部分在TempDB中;内存不足以去换算哈希计算时,哈希连接性能会下降

          示例:StoreUsers表,主键索引Age,另外Id,UserId相等;

   StoreUsers2表,主键索引Age,另外Id,UserId相等    数据库性能优化1

         连接对比>

数据库性能优化1

 

当出现非预期的连接时,建议先考虑Sql语句语法,再考虑统计信息,最后是索引

大部分情况下,我们更加期望的是嵌套连接 

         聚合运算符>:计算一个集合的数据,并返回单个值;Min\Max\Sum\AVG\Count

          聚合运算符对应的物理运算符(流聚合,哈希聚合)

          示例如下:Employee表,总行数10万行,非聚集索引EmployeeName

          Department6表,总行数464行,非聚集索引DepartmentNo

          StoreUsers表,总行数1万行,聚集索引Age

      数据库性能优化1

        流聚合与哈希聚合对比>

    数据库性能优化1

数据库性能优化1

上一篇:(一) MySQL架构


下一篇:sharding-jdbc(一)