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