Java开发人员在编写SQL时常犯的10个错误

Java开发人员将面向对象的思维与命令式思维混合在一起,具体取决于他们的级别:

  • 技能(任何人都可以命令代码)
  • 教条(某些人使用“模式”,即在各处应用模式并为其命名的模式)
  • 心情(真正的面向对象的编写比命令性代码更笨拙。首先)

但是,当Java开发人员编写SQL时,一切都会改变。 SQL是一种声明性语言,与面向对象或命令式思维均无关。 用SQL表达查询非常容易。 最佳或正确地表达它不是那么容易。 开发人员不仅需要重新考虑其编程范例,还需要根据集合论进行思考。

以下是Java开发人员在编写SQL(无特定顺序)时遇到的常见错误:

1.忘记NULL

误解NULL可能是Java开发人员在编写SQL时可能犯的最大错误。 这也是(但不是唯一)由于NULL也称为UNKNOWN的事实。 如果仅将其称为UNKNOWN,它将更容易理解。 另一个原因是在获取数据或绑定变量时,JDBC将SQL NULL映射为Java null。 这可能导致人们认为NULL = NULL(SQL)的行为与null == null(Java)相同

误解NULL的最疯狂的例子之一是当NULL谓词与行值表达式一起使用时

当误解了NOT IN反联接NULL的含义时,会出现另一个细微的问题。

治愈:

训练自己。 每次编写SQL时,除了明确考虑NULL之外,什么都没有:

  • 对于NULL,此谓词正确吗?
  • NULL是否会影响此函数的结果?

2.处理Java内存中的数据

很少有Java开发人员非常了解SQL。 偶尔的JOIN,奇怪的UNION,很好。 但是窗口功能? 分组集? 许多Java开发人员将SQL数据加载到内存中,将数据转换为某种合适的集合类型,并使用冗长的循环结构对该集合执行讨厌的数学运算(至少在Java 8的Collection改进之前 )。

但是某些SQL数据库支持高级(和SQL标准!)OLAP功能,这些功能往往表现得更好,更容易编写。 一个(非标准的)示例是Oracle出色的MODEL子句 。 只需让数据库进行处理,然后仅将结果提取到Java内存中即可。 因为毕竟有些非常聪明的人已经优化了这些昂贵的产品。 因此,实际上,通过将OLAP移至数据库,您可以获得两件事:

  • 简单。 用SQL正确编写可能比用Java正确编写容易
  • 性能。 数据库可能比您的算法更快。 更重要的是,您不必通过网络传输数百万条记录。

治愈:

每次用Java实现以数据为中心的算法时,都要问自己:是否有一种方法可以让数据库为我完成这项工作?

3.使用UNION代替UNION ALL

与UNION相比,UNION ALL需要一个额外的关键字实在可惜。 如果将SQL标准定义为支持以下内容,那就更好了:

  • UNION(允许重复)
  • UNION DISTINCT(删除重复项)

不仅很少需要删除重复项(有时甚至是错误的),而且对于具有许多列的大型结果集来说,这也相当慢,因为需要对两个子选择进行排序,并且每个元组都需要与其后继的元组进行比较。

请注意,即使SQL标准指定了INTERSECT ALL和EXCEPT ALL,几乎所有数据库都不会实现这些不太有用的set操作。

治愈:

每次您编写UNION时,请考虑您是否真的想编写UNION ALL。

4.使用JDBC分页来分页显示较大的结果

大多数数据库通过LIMIT .. OFFSET,TOP .. START AT,OFFSET .. FETCH子句支持某种分页排序结果的方式。 在不支持这些子句的情况下,仍然有可能进行ROWNUM(Oracle)ROW_NUMBER()OVER()筛选(DB2,SQL Server 2008及更低版本) ,这比在内存中分页要快得多。 大偏移量尤其如此!

治愈:

只需使用这些子句,或使用可以为您模拟这些子句的工具(例如jOOQ )。

5.在Java内存中联接数据

从SQL的早期开始,一些开发人员在用SQL表达JOIN时仍然感到不安。 内在的担心JOIN会变慢。 如果基于成本的优化程序选择执行嵌套循环(可能将完整的表加载到数据库内存中),然后再创建联接的表源,则可能是这样。 但这很少发生。 使用适当的谓词,约束和索引,MERGE JOIN和HASH JOIN操作非常快。 都是关于正确的元数据的(为此我经常不能引用Tom Kyte) 。 尽管如此,可能仍有相当多的Java开发人员将来自不同查询的两个表加载到映射中,并以一种或另一种方式将它们连接到Java内存中。

治愈:

如果要通过各种步骤从各个表中进行选择,请再考虑一下是否无法在单个语句中表达查询。

6.使用DISTINCT或UNION从意外的笛卡尔积中删除重复项

通过大量的连接,可以松散跟踪在SQL语句中起作用的所有关系。 具体来说,如果涉及多列外键关系,则可能忘记在JOIN .. ON子句中添加相关谓词。 这可能会导致记录重复,但可能仅在特殊情况下。 然后,某些开发人员可能选择使用DISTINCT再次删除那些重复项。 这是错误的三种方式:

  • 它(可以)解决症状,但不能解决问题。 在边缘情况下也可能无法解决症状。
  • 对于具有许多列的大型结果集,速度很慢。 DISTINCT执行ORDER BY操作以删除重复项。
  • 对于大型笛卡尔乘积,它的速度很慢,这仍将大量数据加载到内存中

治愈:

根据经验,当您收到不需要的重复项时,请始终检查JOIN谓词。 某处可能有一个微妙的笛卡尔积。

7.不使用MERGE语句

这并不是真正的错误,但可能是缺乏知识或对强大的MERGE语句有所恐惧。 一些数据库知道其他形式的UPSERT语句,例如MySQL的ON DUPLICATE KEY UPDATE子句。 但是MERGE确实如此强大,最重要的是在大量扩展SQL标准的数据库( 例如SQL Server)中

治愈:

如果要通过链接INSERT和UPDATE或通过链接SELECT .. FOR UPDATE然后是INSERT或UPDATE进行更新,请再考虑一下。 除了冒险的比赛条件外,您也许可以表达一个更简单的MERGE语句。

8.使用聚合函数代替窗口函数

在引入窗口函数之前,在SQL中聚合数据的唯一方法是使用GROUP BY子句以及投影中的聚合函数。 在许多情况下,此方法效果很好,并且如果需要使用常规数据来丰富聚合数据,则可以将分组查询下推到合并的子查询中。

但是SQL:2003定义了窗口功能,许多流行的数据库供应商都实现了这些功能。 窗口函数可以聚合未分组结果集上的数据。 实际上,每个窗口函数都支持其自己的独立PARTITION BY子句,这是一个很棒的报告工具。

使用窗口功能将:

  • 导致更具可读性的SQL(子查询中较少的专用GROUP BY子句)
  • 提高性能,因为RDBMS可能更容易优化窗口功能

治愈:

当您在子查询中编写GROUP BY子句时,请再次考虑是否可以使用窗口函数来完成此操作。

9.使用内存中排序间接排序

SQL ORDER BY子句支持许多类型的表达式,包括CASE语句,这对于排序间接寻址非常有用。 您可能永远不要对Java内存中的数据进行排序,因为您认为

  • SQL排序太慢
  • SQL排序无法做到

治愈:

如果对内存中的任何SQL数据进行排序,请再次考虑是否无法将排序推入数据库。 这与将分页推送到数据库中很好。

10.一张一张地插入很多记录

JDBC知道批处理,因此您应该使用它。 不要一个接一个地插入数千条记录,每次都重新创建一个新的PreparedStatement。 如果所有记录都放在同一张表中,则使用单个SQL语句和多个绑定值集创建批处理INSERT语句。 根据您的数据库和数据库配置,您可能需要在插入一定数量的记录之后进行提交,以使UNDO日志保持较小。

治愈:

始终批量插入大量数据。

一些有趣的书

关于相似主题的一些非常有趣的书是


翻译自: https://www.javacodegeeks.com/2013/08/10-common-mistakes-java-developers-make-when-writing-sql.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值