2008-08-23 61 views
29

在像TSQL或PLSQL那样编写数据库查询时,我们通常可以选择使用游标遍历行来完成任务,或者制作一次执行同一作业的单个SQL语句。另外,我们可以选择简单地将大量数据拉回到我们的应用程序中,然后使用C#或Java或PHP或其他方式逐行处理它。为什么关系型基于集合的查询比游标更好?

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

回答

15

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

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

0

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

这就是说,要在您的实际案例中获得良好的表现,您可能必须弯曲或违反规则。

噢,另一个原因可能是约束:如果在全部更新后检查约束条件,则增加一个唯一的列可能没有问题,但如果逐个完成则会产生冲突。基于

0

组在一次操作完成 光标的许多操作的光标的行集

12

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

  1. 他们对查询优化器的更多信息优化
  2. 他们可以批量从磁盘中读取
  3. 有参与的回滚,事务日志记录较少等
  4. 个锁少取,从而降低开销
  5. 一套基于逻辑的RDBMS的重点,所以他们已经为它被大量优化(通常,在程序性能为代价)

拉取数据出来尽管如此,中间层对它进行处理可能很有用,因为它可以消除数据库服务器的处理开销(这是最难扩展的,并且通常还在做其他事情)。此外,您通常在中间层没有相同的开销(或收益)。诸如事务性日志记录,内置锁定和阻止等等 - 有时这些是必要的和有用的,有时候它们只是浪费资源。

一个简单的游标与程序逻辑vs.一套基于实例(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 
+0

`UPDATE客户SET AREACODE = AreaCode_Exchange.AreaCode 从客户 JOIN AreaCode_Exchange ON LEFT(Customer.PhoneNumber,3)= AreaCode_Exchange.Exchange WHERE Customer.AreaCode IS NULL”,你能解释一下一个`LEFT (Customer.PhoneNumber,3)`及其功能 – Smart003 2016-03-15 10:36:49

2

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

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

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

15

除了上述“让DBMS做的工作”(这是一个很好的解决方案),也有一些其他的好理由离开查询的DBMS:

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

我同意马特。阅读一些Joe Celko的书籍也有助于做出这些决定。 – 2008-12-01 18:35:32

+2

您忘记提及SQL的查询优化和声明性质;游标和其他基于行的方法可以准确定义如何检索/处理数据,SQL查询只定义要执行的操作 - 然后RDBMS可根据统计数据自由提出最佳计划(例如,根据统计索引寻求可能是更糟或更好的方法,然后索引扫描; RDBMS可以作出区分;基于行的方法不能...) – Unreason 2010-07-08 15:38:38

6

你想要一些真实的例子。我的公司有一个光标,花了40分钟处理30,000条记录(有时我需要更新超过200,000条记录)。没有光标就花了45秒时间完成相同的任务。在另一种情况下,我删除了一个光标,并将处理时间从24小时以上发送到不到一分钟。一个是使用values子句而不是select的插入,另一个是使用变量而不是连接的更新。一个好的经验法则是,如果它是一个插入,更新或删除,你应该寻找一个基于集合的方式来执行任务。游标有它们的用途(或代码不会是他们的第一位),但在查询关系数据库(除了优化使用它们的Oracle)时,它们应该非常少见。他们可以更快的一个地方是根据前面记录的值(运行总计)进行计算。即使如此,应该进行测试。

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

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

1

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

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

1

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

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

看看创建哈希查找表的例子。如果要连接两个表中的一个长度为n和长度为m的其中一个m为较小的表,则构建表可能是m操作。每次查找应该是不变的时间,所以这是n次操作。所以基本上散列连接的效率在m(setup)+ n(lookups)附近。如果你自己做,并假定没有查找/索引,那么对于n行中的每一行,你都必须搜索m个记录(平均等于m/2次搜索)。所以基本上,操作级别从m + n(一次加入一组记录)到m * n/2(通过游标执行查找)。此外,这些操作也是简化。取决于游标类型,获取游标的每一行可能与从第一个表中进行另一次选择相同。

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

另一件事是我听说在Oracle的情况下,它是超级优化的,以执行游标操作,因此它在Oracle中与基于集合的操作与游标相同,与SQL Server中的游标无异。我不是甲骨文的专家,所以我不能肯定地说。但不止一个Oracle人士告诉我,游标在甲骨文中的效率更高。所以如果你为Oracle牺牲你的头胎儿子,你可能不必担心游标,请咨询你当地的高薪Oracle数据库管理员:)

0

真正的答案是去获得E.F. Codd的书之一,刷上relational algebra。然后在Big O notation上找到一本好书。在IT近二十年后,这是IMHO,现代管理信息系统或CS学位的一大悲剧:实际上很少有人研究计算。你知道...“计算机”的“计算”部分?结构化查询语言(及其所有超集)仅仅是关系代数的实际应用。是的,RDBMS已经优化了内存管理和读/写,但程序语言也可以这样说。当我阅读它时,原始问题不是关于IDE,而是关于计算方法与其他方法的效率。

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

0

简单地说,在大多数情况下,让数据库为你做更快/更容易。

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

相关问题