我十分惊讶的发现,我最近的一篇文章——《Java开发者写SQL时常犯的10个错误》——最近在个人博客和个人合做伙伴DZone上很是的受欢迎。(这篇博客)的流行程度说明了几件事: html
不管如何,我以前仓促列出的常见错误还没列完。所以我为你另外准备了10个没那么常见的,但Java开发者在写SQL语句时一样爱犯的错误。 java
有意思的是,在JDBC出现了许多年后的今天,这个错误依然出如今博客、论坛和邮件列表中,即使要记住和理解它是一件很简单的事。开发者不使用PreparedStatements的缘由可能有以下几个: sql
来吧,咱们来破除上面的谣言。96%的案例中,用PreparedStatement比静态声明语句更好。为何呢?就是下面这些简单的缘由: 数据库
(译者注:硬解析的弊端。硬解析即整个SQL语句的执行须要完彻底全的解析,生成执行计划。而硬解析,生成执行计划须要耗用CPU资源,以及SGA资源。在此不得不提的是对库缓存中 闩的使用。闩是锁的细化,能够理解为是一种轻量级的串行化设备。当进程申请到闩后,则这些闩用于保护共享内存的数在同一时刻不会被两个以上的进程修改。在 硬解析时,须要申请闩的使用,而闩的数量在有限的状况下须要等待。大量的闩的使用由此形成须要使用闩的进程排队越频繁,性能则逾低下) express
某些特殊状况下你须要对值进行内联绑定,这是为了给基于成本的性能优化器提示该查询将要涉及的数据集。典型的状况是用“常量”判断: 缓存
而不该该用一个“变量”判断: 性能优化
要注意的是,现代数据库已经实现了绑定数据窥探(bind-variable peeking)。所以,默认状况下,你也能够为你全部的查询参数使用绑定值。在你写嵌入的JPQL或嵌入的SQL时,用JPA CriteriaQuery或者jOOQ这类高层次的API能够很容易也很清晰的帮你生成PreparedStatements语句并绑定值。 mybatis
更多的背景资料: oracle
解决方案: 性能
默认状况下,老是使用PreparedStatements来代替静态声明语句,而永远不要在你的SQL语句嵌入内联绑定值。
这个错误发生的很是频繁,它不光会影响你的数据库执行计划,也会对你的Java应用形成很差的影响。让咱们先看看对后者的影响:
对Java程序的不良影响:
如 果你为了知足不一样DAO层之间的数据复用而select *或者默认的50个列,这样将会有大量的数据从数据库读入到JDBC结果集中,即便你不从结果集读取数据,它也被传递到了线路上并被JDBC驱动器加载到 了内存中。若是你知道你只须要2-3列数据的话,这就形成了严重的IO和内存的浪费。
这个(问题的严重性)都是显而易见的,要当心……
对数据库执行计划的不良影响:
这 些影响事实上可能比对Java应用的影响还要严重。当复杂的数据库要针对你的查询请求计算出最佳执行计划时,它会进行大量的SQL转换(SQL transformation )。还好,请求中的一部分能够被略去,由于它们对SQL连映射或过滤条件起不了什么做用。我最近写了一篇博客来说述这个问题:元数据模式会对Oracle查询转换产生怎样的影响。
如今,给你展现一个错误的例子。想想有两个视图的复杂查询:
1
2
3
4
|
SELECT*
FROM customer_view c
JOIN order_view o
ON c.cust_id = o.cust_id
|
每一个关联了上述关联表引用的视图也可能再次关联其余表的数据,像 CUSTOMER_ADDRESS、ORDER_HISTORY、ORDER_SETTLEMENT等等。进行select * 映射时,你的数据库除了把全部链接表都加载进来之外别无选择,实际上,你惟一感兴趣的数据可能只有这些:
1
2
3
4
|
SELECTc.first_name, c.last_name, o.amount
FROM customer_view c
JOIN order_view o
ON c.cust_id = o.cust_id
|
一个好的数据库会在转换你的SQL语句时自动移除那些不须要的链接,这样数据库就只须要较少的IO和内存消耗。
解决方案:
永远不要用select *(这样的查询)。也不要在执行不一样请求时复用相同的映射。尽可能尝试减小映射到你所真正须要的数据。
须要注意的是,想在对象-关系映射(ORMs)上达成这个目标有些难。
对于性能或SQL语句的正确性来讲,这不算错。可是无论如何,SQL开发者应该意识到JOIN子句不是SELECT语句的一部分。SQL standard 1992 定义了表引用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
6.3 <table reference>
<table reference> ::=
<table name> [ [ AS ] <correlation name>
[ <left paren> <derived column list> <right paren> ] ]
| <derived table> [ AS ] <correlation name>
[ <left paren> <derived column list> <right paren> ]
| <joined table>
7.4 <from clause>
<from clause> ::=
FROM <table reference> [ { <comma> <table reference> }... ]
7.5 <joined table>
<joined table> ::=
<cross join>
| <qualified join>
| <left paren> <joined table> <right paren>
<cross join> ::=
<table reference> CROSS JOIN <table reference>
<qualified join> ::=
<table reference> [ NATURAL ] [ <join type> ] JOIN
<table reference> [ <join specification> ]
|
关联数据库是以表为中心的。许多的操做的某方面都是执行在物理表、链接表或派生表上的。为了有效的写出SQL语句,理解SELECT … FROM子句是以“,”分割表引用是很是重要的。
基于表引用(table references)的复杂性,一些数据库也接受其它类型的复杂的表引用(table references),像INSERT、UPDATE、DELETE、MERGE。看看Oracle实例手册,里面解释了如何建立可更新的视图。
解决方案:
必定要考虑到,通常说来,FROM子句也是一个表引用(table references)。若是你写了JOIN子句,要考虑这个JOIN子句是这个复杂的表引用的一部分:
1
2
3
4
|
SELECTc.first_name, c.last_name, o.amount
FROMcustomer_view c
JOINorder_view o
ONc.cust_id = o.cust_id
|
我 们已经说清了表引用是怎么工做的(看上一节),所以咱们应该达成共识,不论花费什么代价,都应该避免使用ANSI 92标准以前的语法。就执行计划而言,使用JOIN…ON子句或者WHERE子句来做链接谓语没有什么不一样。但从可读性和可维护性的角度看,在过滤条 件判断和链接判断中用WHERE子句会陷入不可自拔的泥沼,看看这个简单的例子:
1
2
3
4
5
6
|
SELECTc.first_name, c.last_name, o.amount
FROM customer_view c,
order_view o
WHERE o.amount > 100
AND c.cust_id = o.cust_id
AND c.language ='en'
|
你能找到join谓词么?若是咱们加入数十张表呢?当你使用外链接专有语法的时候会变得更糟,就像Oracle的(+)语法里讲的同样。
解决方案:
必定要用ANSI 92标准的JOIN语句。不要把JOIN谓词放到WHERE子句中。用ANSI 92标准以前的JOIN语法没有半点好处。
SQL standard 1992 指出like断定应该以下:
1
2
3
4
5
|
8.5 <like predicate>
<like predicate> ::=
<match value> [ NOT ] LIKE <pattern>
[ ESCAPE <escape character> ]
|
当容许用户对你的SQL查询进行参数输入时,就应该使用ESCAPE关键字。尽管数据中含有百分号(%)的状况很罕见,但下划线(_)仍是很常见的:
1
2
3
|
SELECT*
FROM t
WHERE t.xLIKE'some!_prefix%'ESCAPE'!'
|
解决方案:
使用LIKE断定时,也要使用合适的ESCAPE
对于NULLs,这是一个举足轻重的细节!让咱们看看 A IN (X, Y) 真正意思吧:
A IN (X, Y)
is the same as A = ANY (X, Y)
is the same as A = X OR A = Y
When at the same time, NOT (A IN (X, Y)) really means:
一样的,NOT (A IN (X, Y))的真正意思:
NOT (A IN (X, Y))
is the same as A NOT IN (X, Y)
is the same as A != ANY (X, Y)
is the same as A != X AND A != Y
看起来和以前说的布尔值相反同样?其实不是。若是X或Y中任何一个为NULL,NOT IN 条件产生的结果将是UNKNOWN,可是IN条件可能依然会返回一个布尔值。
或者换种说话,当 A IN (X, Y) 结果为TRUE或FALSE时,NOT(A IN (X, Y)) 结果为依然UNKNOWN而不是FALSE或TRUE。注意了,若是IN条件的右边是一个子查询,结果依旧。
不信?你本身看SQL Fiddle 去。它说了以下查询给不出结果:
1
2
3
4
5
|
SELECT1
WHERE 1IN(NULL)
UNIONALL
SELECT2
WHERENOT(1IN(NULL))
|
更多细节能够参考个人上一篇博客,上面写了在同区域内不兼容的一些SQL方言。
解决方案:
当涉及到可为NULL的列时,注意NOT IN条件。
没错,咱们记得处理NULL值的时候,SQL实现了三值逻辑。这就是咱们能用NULL条件来检测NULL值的缘由。对么?没错。
但在NULL条件容易遗漏的状况下。要意识到下面这两个条件仅仅在行值表达式(row value expressions)为1的时候才相等:
NOT (A IS NULL)
is not the same as A IS NOT NULL
若是A是一个大于1的行值表达式(row value expressions),正确的表将按照以下方式转换:
在个人上一篇博客能够了解到更多细节。
解决方案:
当使用行值表达式(row value expressions)时,要注意NULL条件不必定能达到预期的效果。
行值表达式是SQL一个很棒的特性。SQL是一个以表格为中心的语言,表格又是以行为中心。经过建立能在同等级或行类型进行比较的点对点行模型,行值表达式让你能更容易的描述复杂的断定条件。一个简单的例子是,同时请求客户的姓名
1
2
3
|
SELECTc.address
FROM customer c,
WHERE(c.first_name, c.last_name) = (?, ?)
|
能够看出,就将每行的谓词左边和与之对应的右边比较这个语法而言,行值表达式的语法更加简洁。特别是在有许多独立条件经过AND链接的时候就特别有效。行值表达式容许你将相互联系的条件放在一块儿。对于有外键的JOIN表达式来讲,它更有用:
1
2
3
4
|
SELECTc.first_name, c.last_name, a.street
FROM customer c
JOIN address a
ON (c.id, c.tenant_id) = (a.id, a.tenant_id)
|
不幸的是,并非全部数据库都支持行值表达式。但SQL标准已经在1992对行值表达式进行了定义,若是你使用他们,像Oracle或Postgres这些的复杂数据库可使用它们计算出更好的执行计划。在Use The Index, Luke这个页面上有解析。
解决方案:
无论干什么均可以使用行值表达式。它们会让你的SQL语句更加简洁高效。
我又要再次引用Tom Kyte 和 Use The Index, Luke 了。对你的元数据使用限制条件不能更赞了。首先,限制条件能够帮你防止数据质变,光这一点就颇有用。但对我来讲更重要的是,限制条件能够帮助数据库进行SQL语句转换,数据库能够决定。
有些开发者可能认为限制条件会致使(数据库)变慢。但相反,除非你插入大量的数据,对于大型操做是你能够禁用限制条件,或用一个无限制条件的临时“载入表”,线下再把数据转移到真实的表中。
解决方案:
尽量定义足够多的限制条件(constraints)。它们将帮你更好的执行数据库请求。
NoSQL的炒做依然在继续,许多公司认为它们像Twitter或Facebook同样须要更快、扩展性更好的解决方案,想脱离ACID和关系模型横向扩展。有些可能会成功(好比Twitter或Facebook),而其余的也许会走入误区:
看这篇文章:https://twitter.com/codinghorror/status/347070841059692545。
对于那些仍被迫(或坚持)使用关系型数据 库的公司,请不要自欺欺人的认为:“如今的关系型数据库很慢,其实它们是被天花乱坠的宣传弄快的”。实际上,它们真的很快,解析20Kb查询文档,计算 2000行执行计划,如此庞大的执行,所需时间小于1ms,若是你和数据管理员(DBA)继续优化调整数据库,就能获得最大限度的运行。
它们会变慢的缘由有两种:一是你的应用滥用流行的ORM;二是ORM没法针对你复杂的查询逻辑产生快的SQL语句。遇到这种状况,你就要考虑选择像 JDBC、jOOQ 或MyBatis这样的更贴近SQL核心,能更好的控制你的SQL语句的API。
所以,不要认为查询速度50ms是很快或者能够接受的。彻底不是!若是你程序运行时间是这样的,请检查你的执行计划。这种潜在危险可能会在你执行更复杂的上下文或数据中爆发。
SQL颇有趣,同时在各类各样的方面也很微妙。正如个人关于10个错误的博客所展现的。跋山涉水也要掌握SQL是一件值得作的事。数据是你最有价值的资产。带着尊敬的心态对待你的数据才能写出更好的SQL语句。