百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Java开发者写SQL时常犯的10个错误

mhr18 2024-10-17 10:19 25 浏览 0 评论

1、不用PreparedStatements

有意思的是,在JDBC出现了许多年后的今天,这个错误依然出现在博客、论坛和邮件列表中,即便要记住和理解它是一件很简单的事。开发者不使用PreparedStatements的原因可能有如下几个:

  • 他们对PreparedStatements不了解
  • 他们认为使用PreparedStatements太慢了
  • 他们认为写PreparedStatements太费力

来吧,我们来破除上面的谣言。96%的案例中,用PreparedStatement比静态声明语句更好。为什么呢?就是下面这些简单的原因:

  • 使用内联绑定值(inlining bind values)可以从源头上避免糟糕的语句引起语法错误。
  • 使用内联绑定值可以避免糟糕的语句引起的SQL注入漏洞。
  • 当插入更多“复杂的”数据类型(比如时间戳、二进制数据等等)时,可以避免边缘现象(edge-cases)。
  • 你若保持PreparedStatements的连接开启状态而不马上关闭,只要重新绑定新值就可以进行复用。
  • 你可以在更多复杂的数据库里使用adaptive cursor sharing——自适应游标共享(Oracle的说法)。这可以帮你在每次新设定绑定值时阻止SQL语句硬解析。

(译者注:硬解析的弊端。硬解析即整个SQL语句的执行需要完完全全的解析,生成执行计划。而硬解析,生成执行计划需要耗用CPU资源,以及SGA资源。在此不得不提的是对库缓存中 闩的使用。闩是锁的细化,可以理解为是一种轻量级的串行化设备。当进程申请到闩后,则这些闩用于保护共享内存的数在同一时刻不会被两个以上的进程修改。在 硬解析时,需要申请闩的使用,而闩的数量在有限的情况下需要等待。大量的闩的使用由此造成需要使用闩的进程排队越频繁,性能则逾低下)

某些特殊情况下你需要对值进行内联绑定,这是为了给基于成本的性能优化器提示该查询将要涉及的数据集。典型的情况是用“常量”判断:

  • DELETED = 1
  • STATUS = 42

而不应该用一个“变量”判断:

  • FIRST_NAME LIKE “Jon%”
  • AMOUNT > 19.95

要注意的是,现代数据库已经实现了绑定数据窥探(bind-variable peeking)。因此,默认情况下,你也可以为你所有的查询参数使用绑定值。在你写嵌入的JPQL或嵌入的SQL时,用JPA CriteriaQuery或者jOOQ这类高层次的API可以很容易也很清晰的帮你生成PreparedStatements语句并绑定值。

解决方案:

默认情况下,总是使用PreparedStatements来代替静态声明语句,而永远不要在你的SQL语句嵌入内联绑定值。

2、返回太多列

这个错误发生的非常频繁,它不光会影响你的数据库执行计划,也会对你的Java应用造成不好的影响。让我们先看看对后者的影响:

对Java程序的不良影响:

如 果你为了满足不同DAO层之间的数据复用而select *或者默认的50个列,这样将会有大量的数据从数据库读入到JDBC结果集中,即使你不从结果集读取数据,它也被传递到了线路上并被JDBC驱动器加载到 了内存中。如果你知道你只需要2-3列数据的话,这就造成了严重的IO和内存的浪费。

这个(问题的严重性)都是显而易见的,要小心……

对数据库执行计划的不良影响:

这 些影响事实上可能比对Java应用的影响还要严重。当复杂的数据库要针对你的查询请求计算出最佳执行计划时,它会进行大量的SQL转换(SQL transformation )。还好,请求中的一部分可以被略去,因为它们对SQL连映射或过滤条件起不了什么作用。

现在,给你展示一个错误的例子。想一想有两个视图的复杂查询:

每个关联了上述关联表引用的视图也可能再次关联其他表的数据,像 CUSTOMER_ADDRESS、ORDER_HISTORY、ORDER_SETTLEMENT等等。进行select * 映射时,你的数据库除了把所有连接表都加载进来以外别无选择,实际上,你唯一感兴趣的数据可能只有这些:


一个好的数据库会在转换你的SQL语句时自动移除那些不需要的连接,这样数据库就只需要较少的IO和内存消耗。

解决方案:

永远不要用select *(这样的查询)。也不要在执行不同请求时复用相同的映射。尽量尝试减少映射到你所真正需要的数据。

需要注意的是,想在对象-关系映射(ORMs)上达成这个目标有些难。

3、把JOIN当做了SELECT的子句

对于性能或SQL语句的正确性来说,这不算错。但是不管如何,SQL开发者应该意识到JOIN子句不是SELECT语句的一部分。SQL standard 1992 定义了表引用:


关联数据库是以表为中心的。许多的操作的某方面都是执行在物理表、连接表或派生表上的。为了有效的写出SQL语句,理解SELECT … FROM子句是以“,”分割表引用是非常重要的。

基于表引用(table references)的复杂性,一些数据库也接受其它类型的复杂的表引用(table references),像INSERT、UPDATE、DELETE、MERGE。看看Oracle实例手册,里面解释了如何创建可更新的视图。

解决方案:

一定要考虑到,一般说来,FROM子句也是一个表引用(table references)。如果你写了JOIN子句,要考虑这个JOIN子句是这个复杂的表引用的一部分:


4、使用ANSI 92标准之前连接语法

我 们已经说清了表引用是怎么工作的(看上一节),因此我们应该达成共识,不论花费什么代价,都应该避免使用ANSI 92标准之前的语法。就执行计划而言,使用JOIN…ON子句或者WHERE子句来作连接谓语没有什么不同。但从可读性和可维护性的角度看,在过滤条 件判断和连接判断中用WHERE子句会陷入不可自拔的泥沼,看看这个简单的例子:


你能找到join谓词么?如果我们加入数十张表呢?当你使用外连接专有语法的时候会变得更糟,就像Oracle的(+)语法里讲的一样。

解决方案:

一定要用ANSI 92标准的JOIN语句。不要把JOIN谓词放到WHERE子句中。用ANSI 92标准之前的JOIN语法没有半点好处。

5、使用LIKE判定时忘了ESCAPE

SQL standard 1992 指出like判定应该如下:


当允许用户对你的SQL查询进行参数输入时,就应该使用ESCAPE关键字。尽管数据中含有百分号(%)的情况很罕见,但下划线(_)还是很常见的:


解决方案:

使用LIKE判定时,也要使用合适的ESCAPE

6、认为 NOT (A IN (X, Y)) 和 IN (X, Y) 的布尔值相反

对于NULLs,这是一个举足轻重的细节!让我们看看 A IN (X, Y) 真正意思吧:

A IN (X, Y)

is the same as A = ANY (X, Y)

is the same as A = X OR A = Y

When at the same time, NOT (A IN (X, Y)) really means:

同样的,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 条件产生的结果将是UNKNOWN,但是IN条件可能依然会返回一个布尔值。

或者换种说话,当 A IN (X, Y) 结果为TRUE或FALSE时,NOT(A IN (X, Y)) 结果为依然UNKNOWN而不是FALSE或TRUE。注意了,如果IN条件的右边是一个子查询,结果依旧。

解决方案:

当涉及到可为NULL的列时,注意NOT IN条件。

7、认为NOT (A IS NULL)和A IS NOT NULL是一样的

没错,我们记得处理NULL值的时候,SQL实现了三值逻辑。这就是我们能用NULL条件来检测NULL值的原因。对么?没错。

但在NULL条件容易遗漏的情况下。要意识到下面这两个条件仅仅在行值表达式(row value expressions)为1的时候才相等:

NOT (A IS NULL)

is not the same as A IS NOT NULL

如果A是一个大于1的行值表达式(row value expressions),正确的表将按照如下方式转换:

  • 如果A的所有值为NUll,A IS NULL为TRUE
  • 如果A的所有值为NUll,NOT(A IS NULL) 为FALSE
  • 如果A的所有值都不是NUll,A IS NOT NULL 为TRUE
  • 如果A的所有值都不是NUll,NOT(A IS NOT NULL) 为FALSE

解决方案:

当使用行值表达式(row value expressions)时,要注意NULL条件不一定能达到预期的效果。

8、不用行值表达式

行值表达式是SQL一个很棒的特性。SQL是一个以表格为中心的语言,表格又是以行为中心。通过创建能在同等级或行类型进行比较的点对点行模型,行值表达式让你能更容易的描述复杂的判定条件。一个简单的例子是,同时请求客户的姓名


可以看出,就将每行的谓词左边和与之对应的右边比较这个语法而言,行值表达式的语法更加简洁。特别是在有许多独立条件通过AND连接的时候就特别有效。行值表达式允许你将相互联系的条件放在一起。对于有外键的JOIN表达式来说,它更有用:


不幸的是,并不是所有数据库都支持行值表达式。但SQL标准已经在1992对行值表达式进行了定义,如果你使用他们,像Oracle或Postgres这些的复杂数据库可以使用它们计算出更好的执行计划。在Use The Index, Luke这个页面上有解析。

解决方案

不管干什么都可以使用行值表达式。它们会让你的SQL语句更加简洁高效。

9、不定义足够的限制条件(constraints)

我又要再次引用Tom Kyte 和 Use The Index, Luke 了。对你的元数据使用限制条件不能更赞了。首先,限制条件可以帮你防止数据质变,光这一点就很有用。但对我来说更重要的是,限制条件可以帮助数据库进行SQL语句转换,数据库可以决定。

  • 哪些值是等价的
  • 哪些子句是冗余的
  • 哪些子句是无效的(例如,会返回空值的语句)

有些开发者可能认为限制条件会导致(数据库)变慢。但相反,除非你插入大量的数据,对于大型操作是你可以禁用限制条件,或用一个无限制条件的临时“载入表”,线下再把数据转移到真实的表中。

解决方案:

尽可能定义足够多的限制条件(constraints)。它们将帮你更好的执行数据库请求。

10、认为50ms是一个快的查询速度

NoSQL的炒作依然在继续,许多公司认为它们像Twitter或Facebook一样需要更快、扩展性更好的解决方案,想脱离ACID和关系模型横向扩展。有些可能会成功(比如Twitter或Facebook),而其他的也许会走入误区.

对于那些仍被迫(或坚持)使用关系型数据 库的公司,请不要自欺欺人的认为:“现在的关系型数据库很慢,其实它们是被天花乱坠的宣传弄快的”。实际上,它们真的很快,解析20Kb查询文档,计算 2000行执行计划,如此庞大的执行,所需时间小于1ms,如果你和数据管理员(DBA)继续优化调整数据库,就能得到最大限度的运行。

它们会变慢的原因有两种:一是你的应用滥用流行的ORM;二是ORM无法针对你复杂的查询逻辑产生快的SQL语句。遇到这种情况,你就要考虑选择像 JDBC、jOOQ 或MyBatis这样的更贴近SQL核心,能更好的控制你的SQL语句的API。

因此,不要认为查询速度50ms是很快或者可以接受的。完全不是!如果你程序运行时间是这样的,请检查你的执行计划。这种潜在危险可能会在你执行更复杂的上下文或数据中爆发。

总结

SQL很有趣,同时在各种各样的方面也很微妙。正如我的关于10个错误的博客所展示的。跋山涉水也要掌握SQL是一件值得做的事。数据是你最有价值的资产。带着尊敬的心态对待你的数据才能写出更好的SQL语句。

相关推荐

【预警通报】关于WebLogic存在远程代码执行高危漏洞的预警通报

近日,Oracle官方发布了2021年1月关键补丁更新公告CPU(CriticalPatchUpdate),共修复了包括CVE-2021-2109(WeblogicServer远程代码执行漏洞)...

医院信息系统突发应急演练记录(医院信息化应急演练)

信息系统突发事件应急预案演练记录演练内容信息系统突发事件应急预案演练参与人员信息科参与科室:全院各部门日期xxxx-xx-xx时间20:00至24:00地点信息科记录:xxx1、...

一文掌握怎么利用Shell+Python实现完美版的多数据源备份程序

简介:在当今数字化时代,无论是企业还是个人,数据的安全性和业务的连续性都是至关重要的。数据一旦丢失,可能会造成无法估量的损失。因此,如何有效地对分布在不同位置的数据进行备份,尤其是异地备份,成为了一个...

docker搭建系统环境(docker搭建centos)

Docker安装(CentOS7)1.卸载旧版Docker#检查已安装版本yumlistinstalled|grepdocker#卸载旧版本yumremove-ydocker.x...

基础篇:数据库 SQL 入门教程(sql数据库入门书籍推荐)

SQL介绍什么是SQLSQL指结构化查询语言,是用于访问和处理数据库的标准的计算机语言。它使我们有能力访问数据库,可与多种数据库程序协同工作,如MSAccess、DB2、Informix、M...

Java21杀手级新特性!3行代码性能翻倍

导语某券商系统用这招,交易延迟从12ms降到0.8ms!本文揭秘Oracle官方未公开的Record模式匹配+虚拟线程深度优化+向量API神操作,代码量直降70%!一、Record模式匹配(代码量↓8...

一文读懂JDK21的虚拟线程(java虚拟线程)

概述JDK21已于2023年9月19日发布,作为Oracle标准Java实现的一个LTS版本发布,发布了15想新特性,其中虚拟线程呼声较高。虚拟线程是JDK21中引入的一项重要特性,它是一种轻量级的...

效率!MacOS下超级好用的Linux虚拟工具:Lima

对于MacOS用户来说,搭建Linux虚拟环境一直是件让人头疼的事。无论是VirtualBox还是商业的VMware,都显得过于笨重且配置复杂。今天,我们要介绍一个轻巧方便的纯命令行Linux虚拟工具...

所谓SaaS(所谓三维目标一般都应包括)

2010年前后,一个科技媒体的主编写一些关于云计算的概念性问题,就可以作为头版头条了。那时候的云计算,更多的还停留在一些概念性的问题上。而基于云计算而生的SaaS更是“养在深闺人未识”,一度成为被IT...

ORA-00600 「25027」 「x」报错(报错0xc0000001)

问题现象:在用到LOB大对象的业务中,进行数据的插入,失败了,在报警文件中报错:ORA-00600:内部错误代码,参数:[25027],[10],[0],[],[],[],[],[...

安卓7源码编译(安卓源码编译环境lunch失败,uname命令找不到)

前面已经下载好源码了,接下来是下载手机对应的二进制驱动执行编译源码命令下载厂商驱动https://developers.google.com/android/drivers?hl=zh-cn搜索NGI...

编译安卓源码(编译安卓源码 电脑配置)

前面已经下载好源码了,接下来是下载手机对应的二进制驱动执行编译源码命令下载厂商驱动https://developers.google.com/android/drivers?hl=zh-cn搜索NGI...

360 Vulcan Team首战告捷 以17.5万美金强势领跑2019“天府杯“

2019年11月16日,由360集团、百度、腾讯、阿里巴巴、清华大学与中科院等多家企业和研究机构在成都联合主办了2019“天府杯”国际网络安全大赛暨2019天府国际网络安全高峰论坛。而开幕当日最激荡人...

Syslog 日志分析与异常检测技巧(syslog发送日志配置)

系统日志包含有助于分析网络设备整体运行状况的重要信息。然而,理解并从中提取有效数据往往颇具挑战。本文将详解从基础命令行工具到专业日志管理软件的全流程分析技巧,助你高效挖掘Syslog日志价值。Gr...

从Oracle演进看数据库技术的发展(从oracle演进看数据库技术的发展的过程)

数据库技术发展本质上是应用需求驱动与基础架构演进的双向奔赴,如何分析其技术发展的脉络和方向?考虑到oracle数据库仍然是这个领域的王者,以其为例,管中窥豹,对其从Oracle8i到23ai版本的核...

取消回复欢迎 发表评论: