黄东旭关于基础软件产品价值的思考
823
2023-04-17
Java开发人员编写SQL时常犯的十个错误
译者 | 李睿
审校 | 孙淑娟
SQL开发商Data Geeker公司首席执行官Lukas Eder日前表示,他在博客文章列出了Java开发人员在编写SQL时常犯的10个错误。这篇文章得到广泛关注,这让他感到非常惊讶。这种受欢迎程度说明了几点:
SQL对于专业的Java世界有多重要。 忘记一些基本的SQL内容是多么常见。 以SQL为中心的库(例如jOOQ或MyBatis)是如何通过采用SQL来响应市场需求的。
一个有趣的事实是,用户甚至在slick的邮件列表中提到了他写的博客文章。Slick是Scala中的一个不以SQL为中心的数据库访问库。和LINQ(以及LINQ-TO-SQL)一样,它关注的是语言集成,而不是SQL代码生成。
无论如何,Eder列出的一些错误还远远不够,下面将介绍Java开发人员在编写SQL时常犯的10个错误。
1.不使用预处理语句(Prepared Statements)
有趣的是,在JDBC出现多年之后,这种错误或误解仍然出现在博客、论坛和邮件列表中,即使它是关于一个在记忆和理解方面非常简单的的事情。一些开发人员似乎因为以下原因而避免使用预处理语句:
不知道预处理语句。 认为预处理语句速度较慢。 认为编写预处理语句需要花费更多的精力。
首先需要打破以上误区。在96%的情况下,编写预处理语句要比编写静态语句更好。为什么?其原因很简单:
在内联绑定值时,可以省略由错误的字符串连接引起的语法错误。 当内联绑定值时,可以忽略由于字符串连接错误造成的SQL注入漏洞。 当内联更复杂的数据类型(如时间戳、二进制数据等)时,可以避免使用边缘用例。 可以让打开的预处理语句保留一段时间,用新的bind值重用它们,而不是立即关闭它们(例如,在postgres中很有用)。 可以在更复杂的数据库中使用自适应游标共享(***语言)。这有助于防止对每一组新的绑定值进行硬解析SQL语句。
需要注意的是,在极少数情况下,确实需要内联绑定值,以便让数据库的基于成本的优化器了解真正将受到查询影响的数据类型。通常,这会导致“常量”谓词,例如:
deleted = 1status = 42
但它不应该导致“变量”谓词,例如:
first_name like “jon%”amount > 19.95
需要注意的是,现代数据库实现了绑定变量窥视。因此,在默认情况下,还可以为所有查询参数使用绑定值。另外,在编写嵌入式JPQL或嵌入式SQL时,诸如JPA CriteriaQuery或jOOQ等高级API将帮助您生成预处理语句并非常容易和透明地绑定值。
解决办法:
在默认情况下,总是使用预处理语句而不是静态语句,并且永远不要将绑定值内联到SQL中。
2.返回太多的列
这种错误非常常见,可能会在数据库的执行计划和Java应用程序中导致非常糟糕的影响。先看看第二个效果:
(1)对Java应用程序的不良影响
如果选择*(星号)或50列的“默认”集合(在各种数据访问对象之间重用),则需要将大量数据从数据库传输到JDBC结果集。即使没有从结果集中读取数据,它也已经通过网络传输,并由JDBC驱动程序加载到内存中。如果知道只需要2~3个这样的列,这相当浪费IO和内存。
这是显而易见的,但也要小心。
(2)对数据库执行计划的不良影响
这些影响实际上可能比对Java应用程序的影响要严重得多。复杂的数据库在为查询计算最佳执行计划时执行大量SQL转换。很可能查询的某些部分可以被转换掉,因为知道它们不会对投影(选择子句)或过滤谓词产生影响。
考虑一个复杂的选择,它将连接两个视图:
select *from customer_view cjoin order_view o on c.cust_id = o.cust_idonc.cust_id=o.cust_id
连接到上述连接表引用的每个视图可能再次连接来自几十个表的数据,例如customeraddress、order history、order settlement等。考虑到select*投影,数据库别无选择,只能完全加载所有这些联接表,而实际上,唯一感兴趣的是:
select c.first_name, c.last_name, o.amountfrom customer_view cjoin order_view o on c.cust_id = o.cust_id
一个出色的数据库将以一种可以删除大部分“隐藏”连接的方式转换SQL,这将显著地减少数据库中的IO和内存消耗。
解决方法:
从不执行select*。不要为不同的查询重用相同的投影。总是尝试减少投影到真正需要的数据。
注意,用对象关系映射(ORM)很难实现这一点。
3.认为join是select子句
这并不是一个对性能或SQL正确性有很大影响的错误,但是,SQL开发人员应该意识到这样一个事实:join子句本身不是select语句的一部分。sql standard 1992这样定义表引用:
6.3