聊聊数据库优化

写在前面的话

数据库优化涉及到方方面面的知识,每种数据库的架构,优化方式也都有着很大的差别,若是想作好数据库优化要了解数据库的技术架构、存储结构、存储方式、缓存结构、SQL语句执行过程等有很深入的了解。本文只是针对开发人员平常用到的通用的优化方法进行介绍,至于数据库参数调整等数据库相关内容也再也不本文的讨论范围以内。目的是让开发人员对经常使用数据库优化有所了解。mysql

数据库优化的二八法则

二八法则认为,在任何一组东西中,最重要的只占其中一小部分,约20%,其他80%尽管是多数,倒是次要的,如:20%的富人拥有80%的财富。20%的权贵消耗了80%的资源,等等。数据库的优化也符合二八法则:sql

  • 80%的性能问题是由20%的应用致使的。如少许大表的全表扫描致使的性能瓶颈。并非应用一有问题,都须要对系统进行重构,只需优化少部分存在性能问题的功能即可以使系统的性能大幅度提高。
  • 80%的性能问题能够由20%的优化技术所解决。如增长索引,执行计划分析等,能解决绝大部分性能问题

因此咱们若是可以了解常见的数据库优化方法,能够解决开发中遇到的80%问题。数据库

数据库优化该怎么作

在小品里面问大象放冰箱分几步?把冰箱门打开,把大象放进去,把冰箱门关上。缓存

数据库优化分几步?找到具体影响数据库性能的缘由,把问题解决了。bash

发现问题

误区: 个人SQL语句执行的很快,问题确定不会出在我这里。服务器

正解:SQL语句单次执行的快慢并不必定表明语句的好坏。有的语句虽然单次执行效率还比较高,可是随着数据量的增长,并发量的增长极可能成为性能瓶颈。架构

该怎么作?SQL写好了以后看一下这条SQL的执行计划,用事实说话。并发

那么什么是执行计划呢?oracle

SQL是一种傻瓜式语言,每个条件就是一个需求,访问的顺序不一样就造成了不一样的执行计划。数据库必须作出选择,一次只能有一种访问路径。执行计划是一条查询语句在数据库中的执行过程或访问路径的描述。函数

每种数据库都有工具能够查看SQL的执行计划。只有执行计划没问题才表明SQL没问题。若是你能看懂了执行计划。那么恭喜你,优化问题你已经解决了80%。由于发现问题每每比解决问题更困难。

解决问题

上面说到了如何发现问题,其实除了看执行计划还有其它手段,如查看单个SQL执行次数等等,可是,执行计划是开发人员最经常使用最直接的方式,可以帮助咱们解决大部分问题。那么问题发现了以后如何解决呢?

简单的说优化有两个方向:能少作事尽可能少作事,若是不能少作事尽可能利用起服务器的性能。

如何提升服务器性能,能够利用并发的方式让服务器的资源尽可能发挥到极致。有些数据库自己就提供了一些并发机制,如oracle能够经过增长hint来设置SQL的并发数,也能够经过应用程序的并发来尽可能压榨出服务器的性能。固然还能够经过增长服务器,对数据库进行分库分表来提高性能。

另一种方式是尽可能少作事(敲黑板,划重点,这很关键)。

如何让数据库少作事呢?方式也有不少,最多见的是经过索引。曾经有一位在ORACLE从业20余年的专家说了这样一句话,“其实我只懂一点IT知识,IT知识里我只懂一点ORACLE,而ORACLE我也只懂一点数据库,数据库里面只懂点SQL,SQL里面只懂点索引”。可见索引对于数据库优化有多么重要。

聊聊索引

前面也提到索引对于数据库的优化特别重要,接下来聊聊建立和使用索引时的一些注意事项。索引建立的前缀性和可选择性,索引建立的几条建议,常见的索引被抑制状况。

索引的前缀性

先看如下例子假设在员工表(emp)的(ENAME, JOB, MGR)三个字段上建了一个索引,例如索引名叫IDX_1。三个字段分别为员工姓名、工做和所属经理号。而后,写以下一个查询语句,并不断进行查询条件和次序的排列组合,例如:

Select * from emp where ENAME=’a’ and JOB=’b’ and MGR=3;
Select * from emp where JOB=’b’ and MGR=3 and ENAME=’a’;
Select * from emp where JOB=’b’ and ENAME=’a’ and MGR=3;
Select * from emp where JOB=’b’ and MGR=3;
Select * from emp where ENAME=’a’ and MGR=3;
Select * from emp where ENAME=’a’;
Select * from emp where JOB=’b’;
Select * from emp where MGR=3;
复制代码

在各类条件组合状况下,刚才建的索引(IDX_1)是用仍是不用?也就是说对emp表的访问是全表扫描和仍是按索引(IDX_1)访问?

答案是只要有ENAME=’a’条件,就能用上索引(IDX_1),而不是全表扫描。建立复合索引时必定要考虑到索引的前缀性不然会因为没有前缀列在检索条件中致使的全表扫描。

索引的前缀性指的是必须用到索引的第一个字段。

索引的可选择性

索引的可选择性,指的是不重复的索引值(基数)和表记录数的比值。可选择性是索引筛选能力的一个指标。当可选择性越大,索引价值也就越大。

如一张订单表order记录为10万条,表中user_id列的不重复值为10000,order_date列不重复值为1000,则建立在user_id上建立索引的查询效率要比在order_date上建立索引的查询效率高。这是由于,字段值越多,可选性越强,按照索引查询后须要定位的记录越少,查询效率越高。

几条建立索引的建议

数据库最经常使用的索引为B树索引(不一样的数据库实现稍有不一样,例如:oralce建立的是B*树,mysql是B+树),不一样数据库可能还有本身特有的索引如:oracle的位图索引,mysql的hash索引等等。这里咱们只讨论经常使用的B树索引。下面给出几条建立B树索引时设计单字段索引和复合索引的建议:

  1. 分析SQL语句中的约束条件字段,若是约束条件字段比较固定,则优先考虑建立针对多字段复合索引。例如同时涉及到多个字段的条件,则能够考虑创建一个复合索引。
  2. 若是单个字段是主键或惟一字段,或者可选性很是高的字段,尽管约束条件字段比较固定,也不必定要建成复合索引,可建成单字段索引,下降复合索引开销。
  3. 在复合索引设计中,需首先考虑复合索引第一个设计原则:复合索引的前缀性。即SQL语句中,只有复合索引的第一个字段做为约束条件,该复合索引才会启用。
  4. 在复合索引设计中,其次应考虑复合索引的可选性。即按可选性高低,进行复合索引字段的排序。
  5. 若是条件涉及的字段不固定,组合比较灵活,则分别为不一样的列创建单字段索引。
  6. 若是是多表链接SQL语句,注意是否能够在被驱动表(drived table)的链接字段与该表的其它约束条件字段上,建立复合索引。
  7. 经过多种SQL分析工具,分析执行计划并以量化形式评估效果。

常见的索引被抑制状况

在了解了索引建立的规则后根据业务须要建立好了索引,可是经过看语句的执行计划发现仍然走的全表扫描,建立的索引没有被使用。那么索引不被使用都有可能由于什么缘由致使的呢。

1.索引列上有表达式或者函数操做。则索引是失效的。

若有以下语句:

select user_name from user where age -30 = 0

select user_name from user where age  = 30
复制代码

虽然age列上建立了索引,可是第一条语句依然是会按照全表扫描来执行的

2.存在隐式数据类型转换

若有以下语句:

select user_name from user where age  = ‘30’
复制代码

可是user表定义的age列为number类型,在执行查询时发生了隐式类型转换。则索引被抑制。这种状况在开发过程当中没有上一种状况明显。很容易被你们忽视。

3.数据可选性不高

例如user表有10万条数据,可是性别列只有男女两种,这种状况下,即便建立了索引,执行语句时也不会走索引。缘由是根据索引查找出的结果集依然很大,查询效率还不如全表扫描的效率高,这是数据库就会执行全表扫描。

4.忽略的索引的前缀性

如上文所述,执行语句时,忽略了索引的前置性,则执行语句时是不会走索引的。

使用is null或者is not null,null值并无被定义,因此索引会被抑制。

再聊一点

关于执行计划

前面也提到SQL的优化很大程度上要依赖执行计划,那么执行计划是如何生成出来的呢。简单的说,数据库会按期的收集数据库中每一个表的数据量等基础信息。当一条SQL发送到数据库要执行以前,在完成合法性检查以后数据库会根据每张表的数据量,索引等信息经过计算给出一个SQL执行的最优计划。

关于绑定变量

上文提到SQL的执行计划须要经过各类信息计算获得,那么若是我把SQL的执行计划缓存起来。那么每次当同一个SQL执行屡次的时候不就免去了每次计算的代价吗。实际上数据库也正是这样作的。数据库会根据每条SQL的hash值将执行计划缓存到内存。

为了提升缓存SQL的命中率,咱们写SQL的时候更多的使用占位符。而不是直接传值。

若有以下SQL:

select user_name from user where user_id = 1
select user_name from user where user_id = 2
复制代码

这时数据库会当作两条SQL来处理。由于缓存并无命中。作以下修改:

select user_name from user where user_id = ?
复制代码

每次经过绑定不一样的参数则会命中缓存,减小了SQL的解析代价

那么是否是全部的变量都要以绑定变量的方式来传入呢?固然也不是。看下面的例子

若有订单表t_order(user_id, order_date,amount),其中user_id为用户ID,order_date为订单日期

分别建立索引 :

idx_order_1(user_id)
idx_order_2(order_date)
复制代码

若是我但愿查询的SQL为:

select user_id, order_date,amount
from t_order
where user_id >= :num1
and user_id <= :num2
and order_date >= :date1
and order_date <= :date2
复制代码

这时当传入的参数num1和num2之间范围较小极限状况下num1=num2,也就是说我要查询某一我的一段时间的订单,则使用idx_order_2的效率会更高。而当传入的参数num1和num2之间范围较大但date1和date2的范围较小时。如查询某一天全部人的订单,则使用idx_order_1的效率会更高。这时若是咱们使用绑定变量的方式。因为数据库分析执行计划时并不能知道参数的范围是什么。也就不可以给出最优的计划,这时就不建议使用绑定变量方式来传值。

本文也只是对数据库常见的优化方式 进行了讨论。要作好数据库的优化还要在平常开发中多多尝试。

做者介绍

李光明,民生科技有限公司,用户体验技术部Firefly移动金融开发平台Java开发工程师。

相关文章
相关标签/搜索