为什么基于关系集的查询比游标更好?

时间:2008-08-23 12:04:49

标签: sql language-agnostic cursor

当在TSQL或PLSQL之类的东西中编写数据库查询时,我们通常可以选择使用游标迭代行来完成任务,或者制作一次同时执行相同作业的单个SQL语句。

此外,我们可以选择简单地将大量数据拉回到我们的应用程序中,然后使用C#或Java或PHP或其他任何方式逐行处理。

为什么使用基于集合的查询会更好?这个选择背后的理论是什么?什么是基于游标的解决方案及其关系等效的好例子?

11 个答案:

答案 0 :(得分:18)

我知道的主要原因是引擎可以通过跨多个线程运行来优化基于集合的操作。例如,考虑一个快速排序 - 您可以将列表中的列表分成多个“块”,并在每个线程中单独排序。 SQL引擎可以在一个基于集合的查询中使用大量数据执行类似的操作。

执行基于游标的操作时,引擎只能按顺序运行,操作必须是单线程的。

答案 1 :(得分:16)

除了上面的“让DBMS完成工作”(这是一个很好的解决方案)之外,还有其他几个很好的理由将查询留在DBMS中:

  • (主观上)它更容易阅读。稍后查看代码时,您是否愿意尝试使用循环和事物解析复杂的存储过程(或客户端代码),或者而是看一个简洁的SQL语句?
  • 避免网络往返。为什么要将所有数据推送到客户端然后再推回?如果你不需要,为什么还会破坏网络?
  • 这很浪费。您的DBMS和应用服务器需要缓冲部分/全部数据才能使用它。如果你没有无限的记忆,你可能会把其他数据分页;为什么要从内存中挖出可能重要的东西来缓冲一个大多无用的结果集?
  • 为什么不呢?您购买(或以其他方式使用)高度可靠,速度非常快的DBMS。你为什么不用它?

答案 2 :(得分:15)

基于集合的查询(通常)更快,因为:

  1. 他们有更多信息供查询优化器优化
  2. 他们可以从磁盘批量读取
  3. 回滚,事务日志等涉及的日志记录较少。
  4. 减少锁定,减少开销
  5. 基于集合的逻辑是RDBMS的重点,因此它们已经针对它进行了大量优化(通常以牺牲程序性能为代价)
  6. 将数据拉出到中间层以处理它可能很有用,因为它消除了DB服务器的处理开销(这是最难扩展的事情,并且通常也在做其他事情)。此外,您通常在中间层中没有相同的开销(或收益)。诸如事务日志记录,内置锁定和阻塞等等 - 有时这些是必要且有用的,有时它们只是浪费资源。

    一个简单的游标,包含程序逻辑和基于集合的示例(T-SQL),它将根据电话交换分配区号:

    --Cursor
    DECLARE @phoneNumber char(7)
    DECLARE c CURSOR LOCAL FAST_FORWARD FOR
       SELECT PhoneNumber FROM Customer WHERE AreaCode IS NULL
    OPEN c
    FETCH NEXT FROM c INTO @phoneNumber
    WHILE @@FETCH_STATUS = 0 BEGIN
       DECLARE @exchange char(3), @areaCode char(3)
       SELECT @exchange = LEFT(@phoneNumber, 3)
    
       SELECT @areaCode = AreaCode 
       FROM AreaCode_Exchange 
       WHERE Exchange = @exchange
    
       IF @areaCode IS NOT NULL BEGIN
           UPDATE Customer SET AreaCode = @areaCode
           WHERE CURRENT OF c
       END
       FETCH NEXT FROM c INTO @phoneNumber
    END
    CLOSE c
    DEALLOCATE c
    END
    
    --Set
    UPDATE Customer SET
        AreaCode = AreaCode_Exchange.AreaCode
    FROM Customer
    JOIN AreaCode_Exchange ON
        LEFT(Customer.PhoneNumber, 3) = AreaCode_Exchange.Exchange
    WHERE
        Customer.AreaCode IS NULL
    

答案 3 :(得分:8)

你想要一些现实生活中的例子。我的公司有一个光标,花了40多分钟处理30,000条记录(有时我需要更新超过200,000条记录)。没有光标需要45秒才能执行相同的任务。在另一种情况下,我删除了一个光标,并将处理时间从24小时发送到不到一分钟。一个是使用values子句而不是select的插入,另一个是使用变量而不是连接的更新。一个好的经验法则是,如果是插入,更新或删除,您应该寻找一种基于集合的方式来执行任务。

游标有它们的用途(或者代码首先不是它们的代码),但是在查询关系数据库时它们应该是非常罕见的(除了经过优化以使用它们的Oracle)。它们可以更快的一个地方是根据前面记录的值(运行总计)进行计算。甚至应该测试它。

使用游标的另一个有限情况是进行一些批处理。如果您尝试以基于集合的方式一次性执行太多操作,则可以将表锁定到其他用户。如果你有一个真正大的集合,最好将它分解为较小的基于集合的插入,更新或删除,这些插入,更新或删除不会过长地保持锁定,然后使用游标运行集合。

游标的第三种用途是通过一组输入值运行系统存储过程。这仅限于一般的小集合,没有人应该搞乱系统过程,这对管理员来说是可以接受的。我建议不要对用户创建的存储过程执行相同的操作,以便处理大批量并重用代码。编写一个基于集合的版本会更好,因为在大多数情况下性能应该胜过代码重用。

答案 4 :(得分:3)

我认为真正的答案是,与编程中的所有方法一样,它取决于哪一个更好。通常,基于集合的语言将更有效,因为这是它的目的。光标有两个优势:

  1. 您正在更新数据库中的大型数据集,其中锁定行不可接受(可能在生产时间内)。基于集合的更新可以将表锁定几秒钟(或几分钟),其中光标(如果正确写入)不会。光标可以蜿蜒穿过一次更新一行的行,您不必担心影响其他任何事情。

  2. 使用SQL的优势在于,大多数优化工作在大多数情况下都由数据库引擎处理。使用企业级数据库引擎,设计人员已经花了很长的时间来确保系统在处理数据方面的效率。缺点是SQL是一种基于集合的语言。您必须能够定义一组数据才能使用它。虽然这听起来很容易,但在某些情况下并非如此。查询可能非常复杂,引擎中的内部优化器无法有效地创建执行路径,并猜测会发生什么......具有32个处理器的超级强大的盒子使用单个线程来执行查询,因为它不知道如何做其他事情,所以你浪费处理器时间在数据库服务器上,通常只有一个而不是多个应用程序服务器(所以回到原因1,你遇到资源争用与需要在数据库服务器上运行的其他东西)。使用基于行的语言(C#,PHP,JAVA等),您可以更好地控制发生的事情。您可以检索数据集并强制它以您希望的方式执行。 (将数据分开以在多个线程上运行等)。大多数情况下,在数据库引擎上运行它仍然不会有效,因为它仍然必须访问引擎来更新行,但是当你必须做1000多次计算来更新一行时(并且假设你有一百万行),数据库服务器可能会出现问题。

答案 5 :(得分:1)

我认为归结为使用数据库是为了使用而设计的。关系数据库服务器是专门开发和优化的,以最好地响应设置逻辑中表达的问题。

从功能上讲,游标的惩罚因产品而异。一些(大多数?)rdbmss至少部分地构建在isam引擎之上。如果问题是合适的,并且胶合板足够薄,那么使用光标实际上可能同样有效。但在尝试之前,就你的dbms品牌而言,这是你应该非常熟悉的事情之一。

答案 6 :(得分:1)

如前所述,数据库针对集合操作进行了优化。字面上,工程师坐下来调试/调整了该数据库很长一段时间。你优化它们的可能性非常小。如果你有一组可以使用的数据,如批处理磁盘读/写,缓存,多线程,你可以使用各种有趣的技巧。此外,某些操作的开销成本很高,但如果您同时对一堆数据进行操作,则每个数据的成本很低。如果您一次只处理一行,那么很多这些方法和操作都不可能发生。

例如,只需看看数据库加入的方式。通过查看解释计划,您可以看到几种连接方式。很可能使用光标在一个表中逐行进行,然后从另一个表中选择所需的值。基本上它就像一个嵌套循环,只是没有循环的紧密性(很可能编译成机器语言和超级优化)。 SQL Server本身有很多种加入方式。如果对行进行排序,它将使用某种类型的合并算法,如果一个表很小,它可以将一个表转换为哈希查找表,并通过从一个表执行O(1)查找到查找表来执行连接。许多DBMS都有许多连接策略可以帮助您从游标中的一个表中查找值。

请看一下创建哈希查找表的示例。如果要连接两个长度为n且长度为m的表,其中m是较小的表,则构建表可能是m个操作。每次查找应该是恒定时间,因此这是n次操作。所以基本上散列连接的效率大约是m(设置)+ n(查找)。如果你自己做并假设没有查找/索引,那么对于n行中的每一行,你将不得不搜索m个记录(平均而言它等于m / 2个搜索)。所以基本上操作级别从m + n(一次连接一堆记录)到m * n / 2(通过游标进行查找)。操作也是简化。根据游标类型,获取游标的每一行可能与从第一个表中执行另一行选择相同。

锁也会杀了你。如果你有一个表上的游标你正在锁定行(在SQL服务器中,这对于static和forward_only游标来说不那么严重......但是我看到的大多数游标代码只是打开一个游标而没有指定任何这些选项)。如果您在一组中执行操作,行仍将被锁定,但时间较短。此外,优化器可以看到您正在做什么,并且它可能决定锁定整个表而不是一堆行或页面更有效。但如果你逐行去,优化器就不知道了。

另一件事是我听说在Oracle的情况下,它进行了超级优化以进行游标操作,因此对于基于集合的操作与Oracle中的游标和SQL Server中的游标相比,它几乎没有同样的代价。我不是Oracle专家,所以我不能肯定地说。但不止一个Oracle人员告诉我,游标在Oracle中的效率更高。因此,如果您为了Oracle而牺牲了您的长子,您可能不必担心游标,请咨询当地高薪的Oracle DBA:)

答案 7 :(得分:0)

优先考虑在查询中完成工作的想法是数据库引擎可以通过重新构造来优化。这也是为什么你想在你的查询上运行EXPLAIN,看看实际做什么的db。 (例如,利用索引,表格大小,有时甚至知道列中值的分布。)

那就是说,为了在你的实际具体情况下取得好成绩,你可能不得不屈服或违反规则。

哦,另一个原因可能是约束:如果在 all 更新后检查约束,则将一个唯一列增加一个可能没问题,但如果一个一个地完成,则会产生冲突。

答案 8 :(得分:0)

基于集合在一个操作中完成 游标与游标的行集一样多的操作

答案 9 :(得分:0)

真正的答案是获取E.F. Codd书中的一本,并在relational algebra上查阅。然后在Big O notation上获得一本好书。在IT近二十年后,恕我直言,这是现代MIS或CS学位的重大悲剧之一:很少有人真正研究计算。你知道吗......“计算机”的“计算”部分?结构化查询语言(及其所有超集)仅仅是关系代数的实际应用。是的,RDBMS优化了内存管理和读/写,但对于过程语言也是如此。在我读到它时,最初的问题不是关于IDE,软件,而是关于一种计算方法与另一种计算方法的效率。

即使快速熟悉Big O符号,也会开始阐明为什么在处理数据集时,迭代比声明性声明更昂贵。

答案 10 :(得分:0)

简单地说,在大多数情况下,让数据库为您执行此操作会更快/更容易。

数据库的目的是以设定的格式存储/检索/操作数据,并且非常快。您的VB.NET / ASP.NET代码可能远不如专用数据库引擎快。利用这是明智的资源利用。