10种Java开发者编写SQL语句时常见错误
java开发者对于面向对象编程思维与命令行编程思维的协调程度,取决于他们如下几种能力的水平:
技巧(任何人都可以编写命令行形式的代码)
教条(有的人使用“模式 - 模式”的方式,即模式无处不在,并以名字作为标识)
情绪状况(在初期,真正面向对象形式的代码比起命令式代码会更加难懂。)
但是,当java开发人员编写sql语句时,一切都变得不同了。sql是一种说明式语言,与面向对象思想和命令式思想无关。在sql语言中,查询非常容易表达。但它也不是那么容易以最佳或最正确地方式编写出来。开发人员不仅需要重新思考自己的编程模式,还需要从集合论的角度进行深入思考。
以下是java开发人员使jdbc或jooq编写sql语句时,几种常见的错误:
1.忘记了null
误解null的含义可能是java开发人员编写sql最常犯的错误。这有可能是因为null也被称为unknown,但也有其他的原因。当然如果它只被叫做unknown,会更容易理解一些。另一个原因是,jdbc在获取数据,或绑定变量时,sql中的null被映射到java中的null。这可能会导致人们认为类似java中null==null的情况,sql中也存在null= null。
一个更离奇的误解null的例子是,当null谓词用于行值表达式时。
另一个微妙的问题产生与对notin 反连接中null含义的误解。
解决办法
不断的训练自己。要时刻明确null的含义,每次你写sql时,都要考虑:
对于null来说谓词是否正确?
null是否影响该函数的结果?
2.在java内存中处理数据
一些java开发者十分了解sql特性。偶尔join,零散的union,没什么问题。但如果遇到视窗功能,结果集分组等情况又怎么样呢?很多java开发人员会把sql数据加载到内存,把数据转换成一些适合的集合类型,以十分冗长的循环结构在集合上执行恼人数学运算(至少在java 8改进容器之前是这样的)。
但一些sql数据库除了支持sql标准外,还支持先进的olap特性,执行效率更好,且更容易编写。一个非标准的例子就是甲骨文的model子句。只是让数据库进行数据处理过程,将最终获取的结果加载到java内存中。因为一些非常聪明的人已经优化了这些昂贵的产品。所以,事实上,通过向olap数据库上进行迁移,您将得到两个好处:
简洁。它可能使得在sql中编写正确代码会比在java中相对容易
性能。该数据库将可能比你的算法要快。更重要的是,你不必再通过网络传输数百万条记录。
解决办法
每次你在java中实现以数据为中心的算法时,要试着问问自己:有没有办法让数据库执行这些工作,而只把结果交付给我?
3.尽量使用union,而不是union all
相对于union,union all需要额外的关键字显得相形见绌。如果在sql标准已定义如下支持,那将会好很多:
union(允许重复)
union distinct(去掉重复)
一般很少需要去除重复(有时去重甚至是错误的),而且对于具有很多列的大结果集,它往往很慢,因为这两个子查询需要排序,每个元组都需要与随后的元组进行比较。
需要注意的是,即使sql标准指定了intersectall和exceptall,但几乎没有任何数据库实现这些用处不大的操作。
解决办法
你每次写到union时,要考虑下你是否实际上想写的是unionall。
4.使用jdbc分页功能将大量结果分页
大多数数据库都支持通过limit.. offset,top .. start at、offset.. fetch等子句以某种方式对结果进行分页。在没有对这些子句的支持下,但仍然有rownum(oracle)或row_number()over()(db2,sql server 2008和更低版本),这比在内存中分页要快得多。而且这对于大数据集更是明显。
解决办法
只要使用那些子句或工具(如jooq),可以为你模拟上述分页子句。
5.将java内存中实现连接
从sql的发展的初期,一些开发商在面对sql连接时仍然有一种不安的感觉。一直存在着一种固有的恐惧---join速度缓慢。如果基于成本的优化器选择执行嵌套循环,创建一个连接表源之前,加载完整表到数据库内存,那速度确实十分缓慢。但很这少发生。通过适当的谓词,约束和索引,mergejoin和 hashjoin操作是非常快的。这与正确的元数据相关(我不用再举tom kyte的例子了)。然而,也有仍然可能有不少java开发人要会从单独的查询中加载两个表到map容器中,在java内存中以某种方式进行连接操作。
解决办法
如果你从多个步骤的多个表中进行了select操作,那要慎重考虑一下是否可以在一条语句中表达你所需要的查询功能。
6.使用distinct或union从一个笛卡尔积中删除重复
冗长连接的存在,会导致sql语句中起作用的关系显得十分松散。具体地,如果涉及到多列外键关系,很有可能忘记在joinon子句上添加谓词。这可能会导致重复的记录,但也许只在特殊情况下。然后一些开发者可能会选择使用distinct再次删除这些重复记录。这种错误有三种危害:
可能治标不治本。甚至在某些边缘情况下,标都治不了
这在有很多列的大结果集上会十分的缓慢。distinct会执行order by操作来删除重复。
这在大型笛卡尔积中也十分的缓慢,因为这样做仍然会导致在内存中加载大量数据。
解决办法
作为一个经验法则,当你得到不想要的重复结果时,应该首先检查你的连接谓词。因为有可能是在某个地方存在着一个不易察觉的笛卡尔积。
7.不使用merge语句
严格意义上讲,这不是一个真正的错误,可能只是对于功能强大的merge语句缺乏足够的认知或存在着某种恐惧而已。有些数据库包括其他形式的upsert 语句,如mysql的onduplicate key update子句。但merge真的十分强大,最重要的是在数据库中,它在很大程度上扩展了sql标准,如sql server。
解决办法
如果你通过链接insert和update或链接select... for update来实现upserting,那么你要多想一想。抛开与运行条件的风险,你也许可以使用一个简单的merge语句来达到目的。
8.使用了聚合函数,而不是窗体功能
引入窗函数之前,使用groupby子句与投影聚合函数是汇总数据的唯一方式。这在大部分情况下都十分有效,如果聚集后的数据需要由常规的数据进行补充,该分组的查询可以置于连接子查询中。
但是,sql:2003定义了窗口功能,目前很多主流的数据库厂商也纷纷实现了窗口功能。窗口功能可以聚集结果集中未被分组的数据。事实上,每个窗口的功能支持自身独立的partitionby子句,这对于报表类应用是一个非常有用的工具。
使用窗口功能将:
导致更多的可读性sql(减少子查询中非专用group by子句的存在)
提高性能,作为一个rdbms很可能更容易优化其窗口功能。
解决办法
当你在一个子查询写一个groupby子句时,仔细想想这是否能用一个窗口函数来完成。
9.使用内存排序法进行间接排序
在sqlorder by子句支持多种类型的表达式,包括case语句,这对间接排序非常有用。你应该永远可能在java内存中对数据进行排序,因为你认为:
sql排序太慢
sql排序不能做到这一点
解决办法
如果你在内存中对任何sql数据进行排序,请仔细想想,你是否能把排序迁移至数据库中。这和将分页迁移至数据库中的原因一样。
10 一个接一个的插入大量的记录
jdbc包含了批处理,而且你应该使用它。面对成千上万的记录,切勿为每一条记录都创建一个新的preparedstatement来进行插入操作。如果你要将所有记录都插入到同一个表,使用单一的sql语句和多个绑定值集合建立一个批处理的insert语句。根据您的数据库和数据库配置,您可能需要在一定数量的插入的记录后进行提交,为了保持undo日志不过分庞大。
解决办法
始终批量插入大型数据集。
java开发者编写sql语句时常见的10种错误,大家是不是有了大概了解,希望在编写的过程中一定要特别注意!
下一篇: 详解iOS平台调用后台接口的正确姿势