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

在使用TSQL或PLSQL编写数据库查询时,我们经常可以select使用游标遍历行来完成任务,或者制作一次执行同一作业的单个SQL语句。

另外,我们可以select简单地将大量数据拉回到我们的应用程序中,然后使用C#或者Java或者PHP或者其他方法逐行处理。

为什么使用基于集合的查询更好? 这个select背后的理论是什么? 什么是基于游标的解决scheme及其关系等效的一个很好的例子?

我知道的主要原因是基于集合的操作可以通过引擎在多个线程上运行来优化。 例如,考虑一个快速sorting – 您可以将您正在sorting的列表分成多个“块”,并在各自的线程中分别sorting。 在一个基于集合的查询中,SQL引擎可以用大量的数据做类似的事情。

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

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

  • 这是(主观)更容易阅读。 稍后查看代码时,您是否愿意尝试使用循环和事件parsing复杂的存储过程(或客户端代码),还是宁愿查看简明的SQL语句?
  • 它避免了networking往返。 为什么把所有的数据都推给客户,然后再推回去呢? 如果你不需要,为什么要打乱networking?
  • 这是浪费。 你的数据库pipe理系统和应用程序服务器将需要缓冲一些/所有的数据来处理它。 如果你没有无限的内存,你可能会分出其他数据; 为什么从内存中剔除可能重要的东西来缓冲大部分没用的结果集呢?
  • 你为什么不呢? 您购买(或正在使用)高度可靠,非常快速的DBMS。 你为什么不使用它?

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

  1. 他们有更多的信息供查询优化器优化
  2. 他们可以批量从磁盘读取
  3. 涉及回滚,事务日志等的日志较less
  4. 更less的锁被采取,这减less开销
  5. 基于集合的逻辑是RDBMS的重点,所以他们已经对它进行了大量的优化(通常是以牺牲程序性能为代价的)

但是,将数据提取到中间层来处理数据可能很有用,因为它可以消除数据库服务器的处理开销(这是最难扩展的,通常还会做其他事情)。 另外,中间层通常没有相同的开销(或者好处)。 诸如事务性日志logging,内置locking和阻塞等等 – 有时候这些是必要和有用的,其他时候它们只是浪费资源。

一个具有程序逻辑与基于集合的例子(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 

你想要一些真实的例子。 我的公司有一个光标,花了40多分钟处理30,000条logging(有时我需要更新超过20万条logging)。 没有光标就花了45秒时间完成同样的任务。 在另一种情况下,我删除了一个光标,并将处理时间从24小时以上发送到不到一分钟。 一个是使用values子句而不是select的插入,另一个是使用variables而不是连接的更新。 一个好的经验法则是,如果它是一个插入,更新或删除,你应该寻找一个基于集合的方式来执行任务。

游标有它们的用途(或代码不会是他们的第一位),但是在查询关系数据库(除了优化使用它们的Oracle)时,它们应该是非常罕见的。 一个地方,他们可以更快是根据前面的logging(运行总计)的价值进行计算。 即使如此,应该进行testing。

使用游标的另一个有限的情况是做一些批处理。 如果您尝试以基于集合的方式一次执行太多操作,则可以将该表locking给其他用户。 如果你有一个真正的大集合,最好将它分解成更小的基于集合的插入,更新或删除,这些插入不会使锁持续太久,然后使用游标遍历集合。

游标的第三个用途是通过一组input值运行系统存储的特效。 即使这只限于一个小的集合,没有人应该搞乱系统过程,这是pipe理员可以接受的事情。 我不build议用用户创build的存储过程做同样的事情,以处理大批量和重用代码。 编写一个基于集合的版本会更好,因为在大多数情况下,性能应该胜过代码重用。

我认为真正的答案就像所有编程方法一样,取决于哪一个更好。 一般来说,基于集合的语言将会更有效率,因为这是它devise的目的。 有两个地方光标是有利的:

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

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

我认为使用数据库的目的是为了被使用。 关系数据库服务器是专门开发和优化的,以最大限度地回应集合逻辑中expression的问题。

在function上,游标的惩罚将因产品而异。 一些(最?)rdbmss至less部分build立在isam引擎之上。 如果问题是适当的,并且薄木板足够薄,它实际上可以使用光标效率。 但是在尝试之前,这是你应该熟悉的一些事情,就你的品牌dbms而言。

如前所述,数据库针对集合操作进行了优化。 字面上,工程师坐下来debugging/调整数据库很长一段时间。 你优化他们的机会是相当渺茫的。 如果您有一组数据可以像批处理磁盘读取/写入,caching,multithreading一样使用,您可以使用各种有趣的技巧。 另外一些操作的开销成本很高,但是如果你一次处理一堆数据,每个数据的成本就很低。 如果你一次只做一行,那么很多这些方法和操作都不可能发生。

例如,看看数据库join的方式。 通过查看解释计划,您可以看到几种连接方法。 游标最有可能在一个表中逐行排列,然后从另一个表中select需要的值。 基本上它就像一个嵌套的循环,只是没有循环的紧密(这很可能被编译成机器语言和超级优化)。 SQL Server本身就有一大堆的join方式。 如果对行进行sorting,则将使用某种types的合并algorithm,如果一个表很小,则可以将一个表转换为散列查找表,并通过从一个表执行O(1)查find查找表中进行连接。 有许多DBMS具有的连接策略会击败你从游标中的一个表中查找值。

只要看看创build一个哈希查找表的例子。 如果要连接两个长度为n的长度为m,长度为m的长度为m的表格,则build立表格可能是m个操作。 每个查找应该是不变的时间,所以这是n操作。 所以散列连接的效率基本上是m(setup)+ n(lookups)。 如果你自己做,并假定没有查找/索引,那么对于n行中的每一行,你将不得不searchm个logging(平均等于m / 2次search)。 所以基本上,操作级别从m + n(一次join一堆logging)到m * n / 2(通过游标执行查找)。 操作也是简化。 取决于游标types,获取游标的每一行可能与从第一个表中进行另一次select相同。

锁也杀了你。 如果你有一个表上的游标,你正在locking行(在SQL服务器中,对于静态游标和forward_only游标来说不那么严重……但是我所看到的大部分游标代码只是打开游标而没有指定任何这些选项)。 如果你在一个集合中进行操作,行将被locking,但时间较短。 另外,优化器可以看到你在做什么,它可能决定locking整个表格而不是一堆行或页面更高效。 但是,如果你一行一行,优化器不知道。

另一件事是我听说在Oracle的情况下,它是超级优化来做光标操作,所以它在Oracle中的集合操作与游标相比,在SQL Server中是没有相同的惩罚。 我不是Oracle专家,所以我不能肯定地说。 但不止一个甲骨文人士告诉我,游标在甲骨文方面效率更高。 所以,如果你牺牲了你的甲骨文的头胎儿子,你可能不必担心游标,请咨询您当地高薪的Oracle DBA 🙂

喜欢在查询中工作的背后的想法是数据库引擎可以通过重新制定优化来进行优化。 这也是为什么你想在你的查询上运行EXPLAIN,看看数据库实际上在做什么。 (例如利用指数,表格大小,有时甚至是关于列中值的分布的知识)。

也就是说,要想在你的实际案例中取得好的performance,你可能不得不屈服或违反规则。

哦,另一个原因可能是约束:如果在所有更新之后检查约束条件,则将一个唯一的列逐一递增可能没有问题,但是如果逐个完成则会产生冲突。

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

真正的答案是去EF Codd的书之一,刷上关系代数 。 然后得到一本关于大O符号的好书。 在IT近二十年之后,恕我直言,现代MIS或CS学位的大悲剧之一:其实很less有学习计算。 你知道…“计算机”的“计算”部分? 结构化查询语言(及其所有超集)仅仅是关系代数的实际应用。 是的,RDBMS已经优化了内存pipe理和读/写,但程序语言也是这样。 在我看来,原来的问题不是关于IDE,软件,而是关于一种计算方法与另一种计算方法的效率。

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

简而言之,在大多数情况下,让数据库为您执行操作会更快更容易。

数据库在生活中的目的是以设定的格式存储/检索/操作数据,而且速度非常快。 你的VB.NET / ASP .NET代码可能远不如专用数据库引擎快。 利用这是一个明智的使用资源。