文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-13 15:09

关注

SQL开发商Data Geeker公司首席执行官Lukas Eder日前表示,他在博客文章列出了Java开发人员在编写SQL时常犯的10个错误。这篇文章得到广泛关注,这让他感到非常惊讶。这种受欢迎程度说明了几点:  

一个有趣的事实是,用户甚至在slick的邮件列表中提到了他写的博客文章。Slick是Scala中的一个不以SQL为中心的数据库访问库。和LINQ(以及LINQ-TO-SQL)一样,它关注的是语言集成,而不是SQL代码生成。  

无论如何,Eder列出的一些错误还远远不够,下面将介绍Java开发人员在编写SQL时常犯的10个错误。  

1.不使用预处理语句(Prepared Statements)

有趣的是,在JDBC出现多年之后,这种错误或误解仍然出现在博客、论坛和邮件列表中,即使它是关于一个在记忆和理解方面非常简单的的事情。一些开发人员似乎因为以下原因而避免使用预处理语句:  

首先需要打破以上误区。在96%的情况下,编写预处理语句要比编写静态语句更好。为什么?其原因很简单:

需要注意的是,在极少数情况下,确实需要内联绑定值,以便让数据库的基于成本的优化器了解真正将受到查询影响的数据类型。通常,这会导致“常量”谓词,例如:  

但它不应该导致“变量”谓词,例如:

需要注意的是,现代数据库实现了绑定变量窥视。因此,在默认情况下,还可以为所有查询参数使用绑定值。另外,在编写嵌入式JPQL或嵌入式SQL时,诸如JPA CriteriaQuery或jOOQ等高级API将帮助您生成预处理语句并非常容易和透明地绑定值。  

解决办法:

在默认情况下,总是使用预处理语句而不是静态语句,并且永远不要将绑定值内联到SQL中。

2.返回太多的列  

这种错误非常常见,可能会在数据库的执行计划和Java应用程序中导致非常糟糕的影响。先看看第二个效果:

(1)对Java应用程序的不良影响

如果选择*(星号)或50列的“默认”集合(在各种数据访问对象之间重用),则需要将大量数据从数据库传输到JDBC结果集。即使没有从结果集中读取数据,它也已经通过网络传输,并由JDBC驱动程序加载到内存中。如果知道只需要2~3个这样的列,这相当浪费IO和内存。  

这是显而易见的,但也要小心。

(2)对数据库执行计划的不良影响

这些影响实际上可能比对Java应用程序的影响要严重得多。复杂的数据库在为查询计算最佳执行计划时执行大量SQL转换。很可能查询的某些部分可以被转换掉,因为知道它们不会对投影(选择子句)或过滤谓词产生影响。

考虑一个复杂的选择,它将连接两个视图:  

select *
from customer_view c
join order_view o
on c.cust_id = o.cust_id
onc.cust_id=o.cust_id

连接到上述连接表引用的每个视图可能再次连接来自几十个表的数据,例如customeraddress、order history、order settlement等。考虑到select*投影,数据库别无选择,只能完全加载所有这些联接表,而实际上,唯一感兴趣的是:

select c.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*。不要为不同的查询重用相同的投影。总是尝试减少投影到真正需要的数据。  

注意,用对象关系映射(ORM)很难实现这一点。  

3.认为join是select子句

这并不是一个对性能或SQL正确性有很大影响的错误,但是,SQL开发人员应该意识到这样一个事实:join子句本身不是select语句的一部分。sql standard 1992这样定义表引用:  

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>

from子句和连接表可以使用这样的表引用:  

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子句需要一个以逗号分隔的表引用列表,无论它们以何种形式提供。  

根据表引用的复杂性,有些数据库还接受其他语句中的复杂表引用,如插入、更新、删除和合并。

解决方法:

始终将from子句作为一个整体来考虑表引用。如果写一个连接子句,把这个连接子句想象成一个复杂表引用的一部分:

select c.first_name, c.last_name, o.amount
from
customer_view c
join order_view o
on c.cust_id = o.cust_id

4.使用pre-ansi连接语法  

既然已经阐明了表引用是如何工作的,那么无论如何都要避免使用pre-ansi连接语法对于执行计划,如果在join..on子句或where子句中指定连接谓词,通常没有区别。但从可读性和维护的角度来看,对过滤谓词和连接谓词都使用where子句是一个主要的障碍。考虑这个简单的例子:

select c.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'

能发现连接谓词吗?如果加入几十张表呢?当为外部连接应用专有语法(例如oracle的(+)语法)时,情况会变得更糟。  

解决方法:

始终使用ansi-join语法。永远不要将连接谓词放在where子句中。使用pre-ansi连接语法绝对没有好处。

5. 忘记转义like谓词的输入

SQL标准1992指定like谓词如下:  

8.5 <like predicate>
<like predicate> ::=
<match value> [ not ] like <pattern>
[ escape <escape character> ]

当允许在SQL查询中使用用户输入时,几乎总是应该使用escape关键字。虽然百分比符号(%)可能很少被认为是数据的一部分,但下划线(_)很可能是:  

select *
from t
where t.x like 'some!_prefix%' escape '!'

解决方法: 

在使用like谓词时,始终要考虑适当的转义。  

6.认为not(in(x,y))是in(x、y)的布尔逆

这一点很微妙,但对于null来说非常重要!以下回顾一下in(x,y)的真正含义:

a in (x, y)
is the same as a = any (x, y)
is the same as a = x or a = y

同时, 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谓词将导致未知,而in谓词可能仍然返回布尔值。

换句话说,当in(x,y)产生true或false时,not(a in(x、y))仍可能产生unknown,而不是false或true。注意,如果in谓词的右侧是子查询,这也是正确的。

不相信吗?看看这个sql fiddle。它表明以下查询没有产生结果:  

select 1
where 1 in (null)
union all
select 2
where not(1 in (null))

解决方法:  

当涉及nullable列时,要注意not in谓词!

7. 认为not(a为null)与a不为null相同

人们记得SQL在处理null值时实现了三值逻辑。这就是为什么可以使用null谓词来检查null的原因。

但即使是null谓词也很微妙。注意,以下两个谓词仅对度数为1的行值表达式等效: 

not (a is null)
is not the same as a is not null

如果a是一个度数大于1的行值表达式,那么真值表转换为:

解决方法:  

使用行值表达式时,请注意null谓词,它可能无法按预期工作。

8.在支持行值表达式的地方没有使用行值表达式  

行值表达式是一个很好的SQL特性。当SQL是一种以表为中心的语言时,表也以行为中心。行值表达式通过创建可以与具有相同度数和行类型的其他行进行比较的本地特殊行,从而更容易地描述复杂谓词。一个简单的例子是同时查询客户的姓和名。  

select c.address
from customer c,
where (c.first_name, c.last_name) = (?, ?)
Where(c.first_name,c.last_name)=(?,?)

可以看到,这种语法比等价语法(谓词左边的每一列都与右边的相应列进行比较)稍微简洁一些。如果许多独立谓词与and组合在一起,则尤其如此。使用行值表达式允许将相关谓词组合为一个谓词。这对于复合外键上的连接表达式非常有用:  

select c.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这样复杂的数据库可以使用它们来计算更好的执行计划。  

解决方法:

尽可能使用行值表达式。它们将使SQL更简洁,甚至可能更快。  

9.没有定义足够的约束  

要引用TomKyte,再使用索引和Luke。元数据中不能有足够的约束。首先,约束帮助防止数据损坏,这已经非常有用了。但更重要的是,约束将帮助数据库执行SQL转换,因为数据库可以决定:  

一些开发人员可能认为约束是缓慢的。与其相反,除非插入大量大量的数据,在这种情况下,可以禁用大型操作的约束,或者使用没有约束的临时“加载表”,不受约束地将数据脱机传输到实际表。

解决方法:

定义尽可能多的约束。它们将帮助数据库在查询时更好地执行。

10.认为50ms是快速查询执行  

对于NoSQL的炒作仍在继续,许多企业仍然认为他们是Twitter或Facebook,迫切需要更快、更可扩展的解决方案,逃避ACID和关系模型来横向扩展。有些可能会成功(如Twitter或Facebook)。  

对于那些被迫或选择坚持使用经过验证的关系数据库的人来说,如果要和DBA相处得很好并将数据库调到最大的话,不要误以为现代数据库很慢,事实上它们非常快,并在不到一毫秒的时间内解析20kb的查询文本,计算2000行执行计划。  

它们可能会变慢,因为应用程序误用了流行的对象关系映射(ORM),或者因为这一ORM不能为复杂的查询逻辑生成快速SQL。在这种情况下,可能想要选择一个更以SQL为中心的API,例如JDBC,jOOQ或MyBatis,这将重新控制SQL。

所以,不要认为50毫秒的查询执行速度很快,甚至可以接受。事实并非如此。如果在开发时获得了这些速度,确保研究了执行计划。这些数据可能会在生产环境中爆发式增长,因为生产环境中有更复杂的场景和数据。  

结论

SQL非常有趣,但在许多方面也非常微妙。正如Eder之前关于10个常见错误的博客文章所表明的那样,要做到这一点并不容易。但是SQL是可以掌握的。数据是最宝贵的资产。因此需要尊重数据,并编写更好的SQL。

原文10 More Common Mistakes Java Developers Make when Writing SQL,作者:Lukas Eder​

来源:51CTO内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯