经常听到有做应用的朋友抱怨数据库的性能问题,比如非常低的并发,令人崩溃的响应时间,长时间的锁等待,锁升级 , 甚至是死锁,等等。在解决这些问题的过程中,DBA 经常发现应用开发人员对数据库的“误用”。包括 , 返回过多不必要的数据 , 不必要和不适当加锁,对隔离级别的误用和对存储过程的误用等等。但是,面对浩如烟海的数据库知识 , 要求完全掌握 , 对应用开发人员来说也确实枯燥艰深 . 因此,笔者特别提炼对应用开发人员有帮助的 SQL 书写部分,以期望能对数据库开发人员有所帮助。
“根据我们的经验(由很多业界专家证明),在 SQL Server 上取得的性能提高有 80% 来自对 SQL 编码的改进,而不是来自于对于配置或系统性能的调整。”
—凯文 克莱恩等,Transact-SQL Programming 作者
“经验表明 80%-90% 的性能调优是在应用级做的,而不是在数据库级”
—托马斯 白特,Expert One on One: Oracle 作者
本文将主要讨论基于语法的优化以及简简单的查询条件。基于语法的优化指的是为不考虑任何的非语法因素(例如,索引,表大小和存储等),仅考虑在 SQL 语句中对于词语的选择以及书写的顺序。
这一部分,将看一下一些在书写简单查询语时需要注意的通用的规则。
最好的查询语句是将简单的比较操作作用于最少的行上。以下两张表,表 1 和表 2 以由好到差的顺序列出了典型查询条件操作符并赋与权值。
表 1. 查询条件中操作符的权值
| 操作符 | 权值 |
| = | 10 |
| > | 5 |
| >= | 5 |
| < | 5 |
| <= | 5 |
| LIKE | 3 |
| <> | 0 |
表 2. 查询条件中操作数的权值
| 操作数 | 权值 |
| 仅常量字符 | 10 |
| 仅有列名 | 5 |
| 仅有参数 | 5 |
| 多操作数表达式 | 3 |
| 精确数值类型 | 2 |
| 其它数值类型 | 1 |
| 时间数据类型 | 1 |
| 字符数据类型 | 0 |
| NULL | 0 |
根据表 1 和表 2 中分配的权值,可以看出最好的查询条件应该是像下面这样的:
… WHERE smallint_column = 789 |
这个例子得到 27 分,计算如下:
- 左侧只有列名(smallint_column)得 5 分
- 操作数为精确数据类型(smallint_column)得 2 分
- 等号(=)操作符得 10 分
- 右侧是文字字符(789)得 10 分
下面是另外一个例子
… WHERE char_column >= varchar_column || ‘ x ’ |
这种类型的查询权值得分就很低,只有 13 分
- 左侧只有列名(char_column)得 5 分
- CHAR 类型的操作数得 0 分
- 大于等于操作符得 5 分
- 左侧是多操作数表达示得 3 分
- VARCHAR 类型的操作数得 0 分
上面表格中的权值数可能在不同类型的数据库系统中会有所不同,所以记住这些具体数值是没有意义的,只需要了解它们的排序即可。用时越少的比较条件,得分也就越高,这样的比较条件通常是那些操作的行数少或者易于比较的。
传递法则是这样定义的:
IF (A <comparison operator> B) IS TRUE AND (B <comparison operator> C) IS TRUE THEN (A <comparison operator> C) IS TRUE AND NOT (A <comparison operator> C) IS FALSE |
比较运算符包括:=、>、>=、<、<、+, 但不包括:<>、LIKE。
通过传递法则,我们可以看出,可以用 C 来替换 B,而不使表达式的意思发生变化。
下面的两个例子表达了同样的含义,但是第二个表达示要比第一个表达式执行的快。
表达式一:
... WHERE column1 < column2 AND column2 = column3 AND column1 = 5 |
表达式二:
... WHERE 5 < column2 AND column2 = column3 AND column1 = 5 |
大多数的数据库管理系统都会自动的做这样的调整,但是当表达式中含有括号时,它们就不会自动调整了。例如一个如下的 SELECT 语句:
SELECT * FROM Table1 WHERE column1 = 5 AND NOT (column3 = 7 OR column1 = column2) |
如果进行转化的话,会得到如下的语句:
SELECT * FROM Table1 WHERE column1 = 5 AND column3 <> 7 AND column2 <> 5 |
进行这样变化后的语句会比第一个执行的更快。
理想的 SQL 表达式应该采用下面这种通用的格式:
<column> <comparison operator> <literal> |
早些时候,IBM 研究人员将这种查询条件语名命名为”sargable predicates”,因为 SARG 是 Search ARGument 的组合。
根据这一规则,查询条件的左侧应该是一个列名;右侧应该是一个很容易进行查找的值。
遵循这一规则,所有的数据库系统都会将如下的表达式:
5 = column1 |
转换成:
column1 = 5 |
但是当查询条件中包含算术表达式时,只有部分的数据库系统进行转换。
例如:
... WHERE column1 - 3 = -column2 |
转换成:
... WHERE column1 = -column2 + 3 |
还是可以带来查询性能的优化的。
前面,讲的是关于查询条件的一般规则,在这一节中,将讨论如何使用专门的操作符来改进 SQL 代码的性能。
数据库系统按着从左到右的顺序来解析一个系列由 AND 连接的表达式,但是 Oracle 却是个例外,它是从右向左地解析表达式。可以利用数据库系统的这一特性,来将概率小的表达示放在前面,或者是如果两个表达式可能性相同,那么可将相对不复杂的表达式放在前面。这样做的话,如果第一个表达式为假的话,那么数据库系统就不必再费力去解析第二个表达式了。例如,可以这样转换:
... WHERE column1 = 'A' AND column2 = 'B' |
转换成:
... WHERE column2 = 'B' AND column1 = 'A' |
这里假设 column2 = 'B'的概率较低,如果是 Oracle 数据库的话,只需将规则反过来用即可。
