Oracle中的 <> , != 和 ^= 运算符

11

我想知道这些运算符的区别,主要是它们的性能差异。

我查看了SQL中<>和!=之间的区别,但没有关于性能相关的信息。

然后我在dba-oracle.com找到了这个文章,它暗示从10.2开始,性能可能会有很大不同。

我想知道为什么?!=是否总是比<>表现更好?

注意:我们的测试以及生产系统上的性能表明,从<>改为!=对查询返回的时间有很大的影响。我来这里是想问为什么会发生这种情况,而不是它们是否相同。我知道语义上它们是相同的,但实际上它们是不同的。


7
我不太相信博客上张贴的随机电子邮件。你是否真正有任何证据表明存在差异? - Ben
2
自己测试一下,看看你能发现什么。正如@Ben所指出的那样,博客文章并不能代表真相。我从未遇到过这种情况,但另一方面我也没有去寻找它。让我们知道你发现了什么。 - Bob Jarvis - Слава Україні
6
@shanyangqu,我没有看到任何不尊重的言论,也没有任何这方面的意图。虽然dba-oracle.com可能是一个很好的资源,但它链接到一篇博客文章,其中包含一封电子邮件,没有任何尝试来验证该邮件的来源或验证其中的陈述的真实性。实际上,我进行了一个小测试,以查看对一个有40万行的表格使用不同语句的差异会产生什么影响,因为我很感兴趣。这些语句的返回时间在0.1秒内相差无几。在我信任某个陈述之前,我认为需要提供证据,而我自己无法提供这些证据。你是否真正有任何证据表明存在差异? - Ben
1
@shanyangqu,您是不是运行了包含"!="的查询,然后紧接着又运行了相同的查询,但是用的是"<>"?如果是的话,那么前一个查询的结果很可能被缓存到主内存中了,这也就解释了为什么后一个查询执行得非常快。您能否更精确地描述一下您的测试场景? - WojtusJ
1
Dba-oracle.com是一个劣质的网站,它会报道有关Oracle的任何信息,其唯一目的是让自己排名在谷歌搜索结果的顶部并获取点击量。永远不要点击这些链接! - Tony Andrews
显示剩余2条评论
4个回答

29
我已经测试了Oracle中不等运算符的不同语法的性能。我尝试消除所有对测试的外部影响。
我正在使用一个11.2.0.3数据库。没有其他会话连接,数据库在开始测试之前被重启。
创建了一个包含单个表和主键序列的模式。
CREATE TABLE loadtest.load_test (
  id NUMBER NOT NULL,
  a VARCHAR2(1) NOT NULL,
  n NUMBER(2) NOT NULL,
  t TIMESTAMP NOT NULL
);

CREATE SEQUENCE loadtest.load_test_seq
START WITH 0
MINVALUE 0;

为了提高查询性能,表格已被索引。

ALTER TABLE loadtest.load_test
ADD CONSTRAINT pk_load_test
PRIMARY KEY (id)
USING INDEX;

CREATE INDEX loadtest.load_test_i1
ON loadtest.load_test (a, n);

使用序列SYSDATE作为时间戳,以及通过DBMS_RANDOM(A-Z)和(0-99)随机生成数据来添加1000万行到表中。
SELECT COUNT(*) FROM load_test;

COUNT(*)
----------
10000000

1 row selected.

模式已分析以提供良好的统计数据。
EXEC DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'LOADTEST', estimate_percent => NULL, cascade => TRUE);

这三个简单的查询是:-
SELECT a, COUNT(*) FROM load_test WHERE n <> 5 GROUP BY a ORDER BY a;

SELECT a, COUNT(*) FROM load_test WHERE n != 5 GROUP BY a ORDER BY a;

SELECT a, COUNT(*) FROM load_test WHERE n ^= 5 GROUP BY a ORDER BY a;

除了不等运算符的语法(不仅限于 <> 和 !=,还包括 ^=),它们完全相同。

首先,为了消除缓存的影响,每个查询都在不收集结果的情况下运行。

接下来,打开计时和自动跟踪以收集查询的实际运行时间和执行计划。

SET TIMING ON

SET AUTOTRACE TRACE

现在,查询将轮流运行。首先是<>。

> SELECT a, COUNT(*) FROM load_test WHERE n <> 5 GROUP BY a ORDER BY a;

26 rows selected.

Elapsed: 00:00:02.12

Execution Plan
----------------------------------------------------------
Plan hash value: 2978325580

--------------------------------------------------------------------------------------
| Id  | Operation             | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |              |    26 |   130 |  6626   (9)| 00:01:20 |
|   1 |  SORT GROUP BY        |              |    26 |   130 |  6626   (9)| 00:01:20 |
|*  2 |   INDEX FAST FULL SCAN| LOAD_TEST_I1 |  9898K|    47M|  6132   (2)| 00:01:14 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("N"<>5)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      22376  consistent gets
      22353  physical reads
          0  redo size
        751  bytes sent via SQL*Net to client
        459  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         26  rows processed

Next不等于

> SELECT a, COUNT(*) FROM load_test WHERE n != 5 GROUP BY a ORDER BY a;

26 rows selected.

Elapsed: 00:00:02.13

Execution Plan
----------------------------------------------------------
Plan hash value: 2978325580

--------------------------------------------------------------------------------------
| Id  | Operation             | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |              |    26 |   130 |  6626   (9)| 00:01:20 |
|   1 |  SORT GROUP BY        |              |    26 |   130 |  6626   (9)| 00:01:20 |
|*  2 |   INDEX FAST FULL SCAN| LOAD_TEST_I1 |  9898K|    47M|  6132   (2)| 00:01:14 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("N"<>5)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      22376  consistent gets
      22353  physical reads
          0  redo size
        751  bytes sent via SQL*Net to client
        459  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         26  rows processed

最后 ^=
> SELECT a, COUNT(*) FROM load_test WHERE n ^= 5 GROUP BY a ORDER BY a;

26 rows selected.

Elapsed: 00:00:02.10

Execution Plan
----------------------------------------------------------
Plan hash value: 2978325580

--------------------------------------------------------------------------------------
| Id  | Operation             | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |              |    26 |   130 |  6626   (9)| 00:01:20 |
|   1 |  SORT GROUP BY        |              |    26 |   130 |  6626   (9)| 00:01:20 |
|*  2 |   INDEX FAST FULL SCAN| LOAD_TEST_I1 |  9898K|    47M|  6132   (2)| 00:01:14 |
--------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("N"<>5)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
      22376  consistent gets
      22353  physical reads
          0  redo size
        751  bytes sent via SQL*Net to client
        459  bytes received via SQL*Net from client
          3  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
         26  rows processed

这三个查询的执行计划相同,时间分别为2.12秒、2.13秒和2.10秒。

需要注意的是,无论查询中使用哪种语法,执行计划始终显示为<>。

每种操作符语法的测试都重复了十次。以下是各自的时间:

<>

2.09
2.13
2.12
2.10
2.07
2.09
2.10
2.13
2.13
2.10

!=

2.09
2.10
2.12
2.10
2.15
2.10
2.12
2.10
2.10
2.12

^=

2.09
2.16
2.10
2.09
2.07
2.16
2.12
2.12
2.09
2.07

虽然有几百分之几的差异,但这并不重要。三种语法选择的结果相同。 这些语法选择被解析、优化,并在相同时间内返回。因此,在这个测试中,使用哪种语法都没有明显的好处。 “啊,BC”,你说,“在我的测试中,我相信有真正的差异,你不能否认它。” 是的,我说,这完全正确。你没有展示你的测试、查询、数据或结果。所以我对你的结果无话可说。我已经证明了,在其他所有条件相等的情况下,使用哪种语法都没有关系。 “那么为什么我在我的测试中看到有一种更好呢?” 好问题。有几种可能性: 1. 你的测试有缺陷(你没有消除外部因素 - 其他工作负载、缓存等。你没有提供任何信息,我们可以做出知情的决定); 2. 你的查询是一个特殊情况(向我展示查询,我们可以讨论它); 3. 你的数据是一个特殊情况(也许 - 但是怎么回事 - 我们也看不到); 4. 还有其他外部影响。 我通过一个记录和可重复的过程表明,使用一种语法而不是另一种语法没有任何好处。我相信<> !=和^=是同义词。 如果你认为不一样,那就好吧, a)展示一个我可以自己尝试的记录示例 b)使用你认为最好的语法。如果我是正确的,没有任何区别。如果你是正确的,那太酷了,你有一个很小的改进。 “但是Burleson说它更好,我比你、Faroult、Lewis、Kyte和所有其他人都更信任他。” 他说它更好吗?我不这么认为。他没有提供任何明确的例子、测试或结果,只是链接到某个人说!=更好,然后引用了他们的一些帖子。 不要说,要做。

Burleson 暗示说:“这些‘不等于’运算符应该是等价的,但是……” - ypercubeᵀᴹ
测试用例加1。 (但没有赏金,因为那是为能够证明相反的人提供的奖励。尽管我认为您将默认在几个小时内赢得一半的赏金。) - Jon Heller
@ypercube。...但这里有个人说得不一样。Burleson 应该测试过它,并对此负起一定的责任,因为他是一个很有影响力的信息来源。 - user672739
@jonearles。很好。我希望有人能赢得它(但我认为证明这是不可能的)。 - user672739
@Ben。我并没有完全消除外部因素(如其他服务器负载、网络流量等),但这种差异在三个语法测试中是相等的,并且通过每个查询的重复得到缓解,因此我有信心磁盘活动不会偏向或阻碍某个特定的语法。 - user672739
显示剩余3条评论

19

您参考了Burleson网站上的文章。 您有跟进到Oracle-L档案库的链接吗? 您是否阅读了回复Burleson引用的其他电子邮件?

我认为您没有,否则您不会问这个问题。 因为'!='和'<>'之间没有根本区别。 最初的观察几乎肯定是由数据库中的环境条件造成的偶然事件。 阅读Jonathan LewisStephane Faroult的回复以了解更多信息。


 

“尊重不是程序员需要具备的东西,而是任何人类都应该具备的基本态度”

在一定程度上,当我们在街上遇到陌生人时,当然应该礼貌地对待他们并尊重他们。

但是,如果那个陌生人希望我按特定方式设计我的数据库应用程序以“提高性能”,那么他们应该有令人信服的解释和一些经过严密测试的案例来支持它。 来自某个随机个体的孤立轶事是不够的。


就我个人而言,关于Oracle方面的任何事情,我都会听从Jonathan Lewis的建议。 - Bob Jarvis - Слава Україні
2
@Bob - 从某种意义上说,我不确定他是否希望你这样做。Jonathan Lewis等人如此有用的原因是他们所说的内容通常是可验证和可重现的。你应该检查一下他们所说的内容,而不总是“听信他们的话”,这样做至少对于巩固记忆来说是一个很好的方法。;-) - user533832
有时候我希望互联网有一个删除按钮,可以用来删除这样的时刻。 - ShoeLace

12
该文章的作者虽然是一位书籍作者,并提供了一些有用的信息,但他在准确性方面并没有很好的声誉。在这种情况下,该文章只是提到了一份知名Oracle邮件列表中一位人员所做的观察。如果你阅读回复,你会看到帖子的假设受到了挑战,但没有准确性的假设。以下是一些摘录:

尝试通过explain plan(或autotrace)运行查询,看看会出现什么...按照此说明,“!=”被认为与“<>”相同...Jonathan Lewis

Jonathan Lewis是Oracle社区中备受尊敬的专家。

只是出于好奇...查询优化器是否会为两个查询生成不同的执行计划?问候,克里斯

.

可能是绑定变量窥视在起作用吗?编写“!=”而不是“<>”的确定效果是强制重新解析。如果在第一次执行时:id的值有所不同,并且如果您在claws_doc_id上具有直方图,则可能是原因。如果您告诉我,claws_doc_id是主键,那么我会问您计数的目的是什么,特别是在存在子句的查询与外部查询无关并且将返回相同结果的情况下:id是什么。看起来像是轮询查询。周围的代码必须很有趣。

Stéphane Faroult

.

我非常确定词法解析器会将“!=”转换为“<>”或将“<>”转换为“!=”,但我不确定是否会影响SQL文本是否将匹配存储的轮廓。

.

explain plan是否相同?成本一样吗?

以下回复来自原帖发布者。

乔纳森,谢谢你的回答。我们确实对语句的两个版本进行了解释计划,并且它们完全相同,这就是让人困惑的地方。根据文档,两种不等式形式是相同的(以及“^=”和我无法输入的另一个),因此我无法理解性能上的任何差异。

斯科特·卡南

.

这是一个不可包容的小测试,但至少在10.1.0.2中,它会将其削减为“<>”(请注意每个计划的过滤器行)

.

您是否有任何存储轮廓?存储轮廓与字面匹配,因此如果您对具有“!=”的SQL拥有一个存储的轮廓,并且没有针对具有“<>”或颠倒情况的SQL的存储的轮廓,则存储的轮廓可能使用提示? (尽管这样想想,如果执行存储的轮廓,您的EXPLAIN PLAN应该显示提示吗?)

.

你是否试过超越只解释和自动跟踪,并运行完整的10046级12跟踪,以查看较慢版本花费的时间?这可能会为该主题提供一些启示。此外,请确保验证说明计划在10046跟踪文件(而不是使用EXPLAIN =选项生成的那些)和v $ sqlplan中完全相同。 autotrace和解释存在某些“功能”,可能导致它不能为您提供准确的解释计划。

问候,布兰登

.

这种现象完全可以重现吗?

您是否检查过计划的filter_predicates和access_predicates,而不仅仅是结构。我不希望出现任何差异,但是如果您运气不好,则谓词顺序的更改可能导致CPU使用率发生显着变化。

如果没有差异,请启用rowsource统计信息(alter session set“_rowsource_execution_statistics”= true),然后运行查询,然后从V $ sql_plan中获取执行计划,并连接到v $ sql_plan_statistics,以查看有关last_starts、last_XXX_buffer_gets的数字,最后的磁盘读取,最后的经过时间等,是否给您提供了线索。

如果您使用的是10gR2,则可以使用/*+ gather_plan_statistics */提示,而不是“alter session”。

问候乔纳森·路易斯

此时,主题已死亡,我们没有看到原始发布者的进一步帖子,这让我相信要么原始发布者发现了他们没有真实的假设,要么没有进行进一步的调查。

我还要指出,如果您解释计划或自动跟踪,您将看到比较始终显示为<>

这是一些测试代码。如果您喜欢,可以增加循环迭代次数。由于服务器活动的其他活动可能会使一侧或另一侧获得更高的数字,因此您可能会看到其中一个运算符始终优于另一个运算符。

DROP TABLE t1;
DROP TABLE t2;
CREATE TABLE t1 AS (SELECT level c1 FROM dual CONNECT BY level <=144000);
CREATE TABLE t2 AS (SELECT level c1 FROM dual CONNECT BY level <=144000);

SET SERVEROUTPUT ON FORMAT WRAPPED

DECLARE
   vStart  Date;
   vTotalA Number(10) := 0;
   vTotalB Number(10) := 0;
   vResult Number(10);
BEGIN   
   For vLoop In 1..10 Loop
      vStart := sysdate;
      For vLoop2 In 1..2000 Loop
         SELECT count(*) INTO vResult FROM t1 WHERE t1.c1 = 777 AND EXISTS
            (SELECT 1 FROM t2 WHERE t2.c1 <> 0);
      End Loop;
      vTotalA := vTotalA + ((sysdate - vStart)*24*60*60);

      vStart := sysdate;
      For vLoop2 In 1..2000 Loop
         SELECT count(*) INTO vResult FROM t1 WHERE t1.c1 = 777 AND EXISTS
            (SELECT 1 FROM t2 WHERE t2.c1 != 0);
      End Loop;
      vTotalB := vTotalB + ((sysdate - vStart)*24*60*60);

      DBMS_Output.Put_Line('Total <>: ' || RPAD(vTotalA,8) || '!=: ' || vTotalB);
      vTotalA := 0;
      vTotalB := 0;
   End Loop;

END;

“文章作者虽然是一位书籍作者和提供了一些有用信息的人,但他在准确性方面并没有良好的声誉”这句话是从哪里来的?你必须提供一些证据。如果没有证据,恐怕我不得不称你为撒谎者。 - Junchen Liu
2
你对比过执行计划、自动跟踪结果和10046级别12跟踪输出了吗?你向Oracle提出了SR申请吗?你能否提供一个类似于我的测试用例,但是展示不等式形式的一种受益于另一种的区别性结果? - Leigh Riffel
7
如果没有证据,你可以不相信我。以下是一些支持我的结论的事实:http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:47466211228419 http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:35336203098853 http://oraclesponge.wordpress.com/2005/04/11/banned-by-burleson/ http://jonathanlewis.wordpress.com/2006/12/28/missing-the-point/ http://jonathanlewis.wordpress.com/2007/01/02/superfluous-updates/ http://jonathanlewis.wordpress.com/2007/11/10/analysing-statspack8/ http://jonathanlewis.wordpress.com/2008/06/08/scientific-method/请注意,这些链接仅供参考,不构成解释或说明。 - Leigh Riffel
5
http://www.jlcomp.demon.co.uk/untested.html: 这篇文章主要是针对不经测试就直接在生产环境中使用Oracle数据库的做法进行了讨论。文章指出,这种做法会导致性能问题,同时也可能破坏系统的稳定性和可靠性。http://kevinclosson.wordpress.com/2007/07/17/oracle-doesnt-scale-without-multiple-log-writer-processes/: 这篇文章说明了Oracle数据库缺乏多个日志写入进程时无法实现良好的性能扩展。作者在文章中提到了一些关于多个日志写入进程的建议,并通过实验结果支持了这些建议。http://www.freelists.org/post/oracle-l/Recent-Email-from-Don-Burleson,1: 这篇文章主要内容是Don Burleson最近发布的一封电子邮件的摘要。他在邮件中提到了一些关于Oracle数据库管理的观点和建议。http://jonathanlewis.wordpress.com/2010/08/26/dmca/: 这篇文章讨论了数字千年版权法(DMCA)如何影响技术研究人员对Oracle数据库进行分析。作者还分享了自己在这方面的经历,并给出了一些调查数据以支持他的观点。http://jonathanlewis.wordpress.com/2008/06/10/firefox/: 这篇文章探讨了使用Firefox浏览器时可能会遇到的一些问题。作者提供了一些关于如何优化Firefox性能的建议,以及如何避免一些常见的陷阱和错误。http://www.oaktable.net/content/burleson-buys-bmc: 这篇文章宣布了Oracle数据库专家Don Burleson购买BMC软件的消息,并讨论了这个决定的背景和影响。http://nlitchfield.blogspot.com/2005_04_01_archive.html: 这是一个博客页面的存档,作者在其中分享了自己在Oracle数据库领域的经验和见解,同时也探讨了一些相关技术话题。 - Leigh Riffel
这是Jonathan和Burleson之间的一场狗斗,很遗憾Jonathan使用他的WordPress攻击其他人。钱?客户?嫉妒,竞争?我不在乎。但似乎你所有的结论都来自于Jonathan的口中,这是个人恩怨,你不能仅凭一个人的话反对另一个人。 - Junchen Liu
6
啥?再检查一下链接。我引用了五个人,不包括评论中的众多证实。 - Leigh Riffel

4

程序员会使用!=

数据库管理员会使用<>

如果存在不同的执行计划,可能是因为每个符号的查询缓存或统计信息存在差异。但我并不认为是这样的。

编辑:

我的意思是,在复杂的数据库中可能会出现一些奇怪的副作用。我对Oracle不够熟悉,但我想这里有一个查询编译缓存,类似于SQL Server 2008 R2。如果一个查询被编译成新查询,那么数据库优化器将根据当前的统计信息计算一个新的执行计划。如果统计信息发生了变化,它将导致一个不同的、可能更糟糕的计划。


5
有时候我会发现自己在混淆一些东西 :) - edze
3
实际上,(ANSI) SQL 标准定义了<>运算符。!= 是一些(如果不是全部)数据库管理系统的扩展。 - user330315
2
就个人而言,我在编写 SQL 时总是使用 <>。在编写 C 或其衍生语言时,我使用 !=。在编写 Smalltalk 时,我使用 ~=。在编写 PL/I(我已经近25年没有接触过了)时,我使用 ^=(实际上应该是一种时髦的 IBM 式“带有垂直下降符号”的字符,而不是插入符号,但你能怎么办呢?)。有很多不同的方式来表示“不相等”——我似乎记得这是 COBOL 中使用的。 :-) - Bob Jarvis - Слава Україні
4
我曾在30年前当过程序员。在学习SQL时,"<>"是可用的,但"!="则不行。因此我养成了在这种情况下使用“DBA风格”的习惯。但这并不代表我是DBA或程序员。拜托了,孩子们。 - Walter Mitty
7
" != faster then <> has been tested proven". Horse feathers! Where are the benchmarks?这段话的意思是:“'!= faster then <> has been tested proven'是胡说八道!哪里有测试数据支持?”请注意,“!=”表示不等于,“<>”表示小于或大于,原文可能存在笔误。 - APC
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接