游标sql server

介绍 (Introduction)

在上一篇文章中,我们讨论了如何设置基本游标。 我们解释了游标是基于行的操作,它采用给定的SELECT语句并将数据处理分解为循环执行。 没有数据库管理背景或在学习时不喜欢数据库类的人通常会发现SQL Server游标很方便并且编写起来很有趣。 这是因为它们摆脱了编写大多数T-SQL脚本时常见的基于集合的逻辑的束缚。 出于这个原因,我们经常发现写入应用程序业务逻辑SQL Server游标,这实在令人遗憾,因为它们是真正的性能消耗。 是的,有时候可以使用游标,可以通过使用其不同的类型对其进行微调,但是,根据一般经验,应该不惜一切代价避免使用游标。

性能问题 (Performance problems)

SQL Server cursors are notoriously bad for performance. In any good development environment people will talk about cursors as if they were demons to be avoided at all costs. The reason for this is plain and simple; they are the best way to slow down an application. This is because SQL Server, like any good relational database management system (RDBMS), is optimized for set-based operations. Take this simple SELECT statement as an example:

众所周知,SQL Server游标会降低性能。 在任何良好的开发环境中,人们都会谈论游标,就好像不惜一切代价避免游标一样。 原因很简单。 它们是减慢应用程序速度的最佳方法。 这是因为SQL Server与任何良好的关系数据库管理系统(RDBMS)一样,都针对基于集合的操作进行了优化。 以这个简单的SELECT语句为例:


SELECT  *
FROM    AdventureWorks2012.Sales.SalesOrderDetail
WHERE   ModifiedDate BETWEEN '2008-07-15 00:00:00.000' AND '2008-07-31 00:00:00.000'

When you write a SELECT statement like this (that returns 1051 rows) to fetch a bunch of data from that database the system receives the statement and creates or uses an existing query plan, then it uses indexes to locate the data on the disk, fetches the data in one foul swoop and returns the data as a set. If your indexes are correctly placed the query can be sped up. In the case above if the ModifiedDate field was included in an index it would run faster.

当您编写这样的SELECT语句(返回1051行)以从该数据库中获取一堆数据时,系统会接收该语句并创建或使用现有的查询计划,然后使用索引在磁盘上定位数据,然后进行提取一口气将数据返回,然后将数据作为一组返回。 如果正确放置了索引,则可以加快查询速度。 在上述情况下,如果ModifiedDate字段包含在索引中,它将运行得更快。

When running this query and turning time statistics on (SET STATISTICS TIME ON) one can see that the entire process takes less than a second:

运行此查询并打开时间统计信息( SET STATISTICS TIME ON )时,可以看到整个过程花费的时间不到一秒钟:

SQL Server Execution Times:
    CPU time = 15 ms, elapsed time = 87 ms.

SQL Server执行时间:
CPU时间= 15毫秒,经过时间= 87毫秒。

Now let’s say you wanted (for some reason) to replace your WHERE statement with a variable that can be used to call a single row each time but 1051 times you can use a cursor to do so:

现在,假设您要(出于某种原因)将WHERE语句替换为一个变量,该变量可用于每次调用一行,但您可以使用游标来执行1051次:


DECLARE @rowguidVar UNIQUEIDENTIFIER  -- prepare unique ID variable to use in the WHERE statement belowDECLARE test_cursor CURSOR FOR
SELECT rowguid
FROM   AdventureWorks2012.Sales.SalesOrderDetail
WHERE  ModifiedDate BETWEEN '2008-07-15 00:00:00.000' AND '2008-07-31 00:00:00.000'
--This is the same query as above except we SELECT only the ID for each rowOPEN test_cursor
FETCH NEXT FROM test_cursor INTO @rowguidVar
--This is the start of the cursor loop.
WHILE @@FETCH_STATUS = 0
BEGIN   SELECT *FROM            Sales.SalesOrderDetailWHERE    rowguid = @rowguidVar
-- Here we select on row and then move onto the next row ID and loopFETCH NEXT FROM test_cursor INTO @rowguidVar
ENDCLOSE test_cursor
DEALLOCATE test_cursor
-- Don't forget these statements which flush the cursor from memory

Results:

结果:

This cursor will fetch exactly the same set of data but it does it on a row by row basis, and it takes heck of a lot longer to do so, as a matter of fact 48 seconds as opposed to 87 milliseconds, that’s 55172% slower! This is because the set-based logic for which RDBMS systems like SQL Server are optimized is completely broken and the entire query process has to be repeated for each row.

该游标将获取完全相同的数据集,但会逐行读取,并且花费的时间更长,实际上是48秒而不是87毫秒,这慢了55172% ! 这是因为针对RDBMS系统(如SQL Server)进行优化的基于集合的逻辑已完全中断,并且必须对每一行重复整个查询过程。

那么为什么它们存在呢? (So why do they exist?)

SQL Server cursors and any other type of cursors date back to before procedural programming languages could handle sets of data and required to be split into rows (E.g. COBOL, FORTRAN, old style C etc.) So in that regard they are just plain old-fashioned. However, other than for backwards compatibility they can still serve us well in the right situations. One such time would be when you want to write a script to restore a bunch of databases from backup files on a disk. In this case you can write a cursor to collect the database names and run a RESTORE DATABASE command for each database in a one-by-one fashion. Another time this may be useful is when you need to update an entire column of a large table that is constantly being queried in a production environment. Doing this on a row-by-row basis would avoid locks and waits for other users and your UPDATE query while concurrent operations are happening on the same pages of data. However, even in this case it is usually preferable to write a WHILE loop to update sets of data (i.e. on a 1000 by 1000 row basis). This would also avoid too many locks and would do the job quicker.

SQL Server游标和任何其他类型的游标的历史可以追溯到过程编程语言可以处理数据集并需要拆分为行(例如COBOL,FORTRAN,旧样式C等)之前,因此在这方面,它们只是普通的旧-老式的。 但是,除了向后兼容以外,它们在正确的情况下仍然可以很好地为我们服务。 这样的时间就是您想要编写脚本以从磁盘上的备份文件中还原一堆数据库的时候。 在这种情况下,您可以编写一个游标以收集数据库名称,并以一对一的方式为每个数据库运行RESTORE DATABASE命令。 另一个有用的时间是当您需要更新在生产环境中不断查询的大表的整个列时。 逐行执行此操作将避免锁定,并在其他操作和相同的数据页上同时进行操作时等待其他用户和UPDATE查询。 但是,即使在这种情况下,通常最好编写一个WHILE循环来更新数据集(即以1000 x 1000行为基础)。 这也可以避免过多的锁定,并且可以更快地完成工作。

To illustrate how a while loop works I have massaged the above query example to return the same data again but this time, instead of row-by-row or a full set, it does something in between and returns 100 rows at a time.

为了说明while循环是如何工作的,我整理了上面的查询示例以再次返回相同的数据,但这一次,它不是在逐行或完整的数据集之间进行操作,而是一次返回100行。


DECLARE @GUIDS TABLE (rowguid UNIQUEIDENTIFIER PRIMARY KEY)
--Here we create an indexed table variable to store all the GUIDsINSERT INTO    @GUIDS
SELECT         rowguid
FROM           AdventureWorks2012.Sales.SalesOrderDetail
WHERE          ModifiedDate BETWEEN '2008-07-15 00:00:00.000' AND '2008-07-31 00:00:00.000'
--Here we insert all our GUIDs into the variable/temporary tableWHILE EXISTS    (SELECT rowguidFROM @GUIDS)
--This is a basic WHILE loop that runs as long as there is data in the variable tableBEGIN
SELECT *
FROM   AdventureWorks2012.Sales.SalesOrderDetail
WHERE  rowguid IN   (SELECT TOP 100 * FROM  @GUIDS)
--We SELECT the top 100 rows that are in our variable tableDELETE TOP (100)
FROM @GUIDS
--This deletes the 100 rows that we have just selected\END
--If there is still data in the variable table we return to the BEGIN point and process the next 100

Even this clumsy WHILE loop is blisteringly fast in comparison to the SQL Server cursor approach. It takes less than a second but is closer to 800ms than 87ms as is the case for the pure set-based query.

与SQL Server游标方法相比,即使是这种笨拙的WHILE循环也非常快。 与基于纯集合的查询一样,它花费的时间不到一秒钟,但比87ms短了800ms。

结论 (Conclusion)

People are right to loath cursors. If it becomes normal for you to use cursors in your T-SQL whilst building the business logic of an application you are heading off down a path to disastrous performance. Imagine, for example, you wrote a stored procedure that returns results based on a cursor and then you write another stored procedure using a cursor that calls the first one. This is called nested cursors and it is a perfect way to bog down expensive/performant server equipment with sloppy, badly performing code.

人们有权讨厌游标。 如果在构建应用程序的业务逻辑时在T-SQL中使用游标变得很正常,那么您将走上一条灾难性性能的道路。 例如,想象一下,您编写了一个存储过程,该存储过程基于游标返回结果,然后您使用调用第一个游标的游标编写了另一个存储过程。 这被称为嵌套游标,它是用草率的,性能不佳的代码停滞昂贵/高性能服务器设备的理想方法。

So, avoid cursors more than you would avoid your mother-in-law and only use them when you have mastered set-based T-SQL and you know that a row-by-row approach is needed and only for a one off maintenance/patch-script operation.

因此,避免游标的方法要比避免岳母的方法多,而仅在您掌握了基于集合的T-SQL并且知道需要逐行方法且仅用于一次维护/维护时,才使用游标。补丁脚本操作。

有用的资源 ( Useful resources)

  • SQL Server cursor tutorial SQL Server游标教程
  • Transact-SQL CursorsTransact-SQL游标

翻译自: https://www.sqlshack.com/sql-server-cursor-performance-problems/

游标sql server

游标sql server_SQL Server游标性能问题相关推荐

  1. 游标sql server_SQL Server游标教程

    游标sql server 介绍 (Introduction) 大多数使用Microsoft SQL Server的人至少会听说过游标,而且即使人们基本了解SQL Server游标的作用,他们也不总是确 ...

  2. 游标sql server_SQL Server游标属性

    游标sql server A SQL Server cursor is a database object that is used to process the data in a set row ...

  3. 游标sql server_了解游标并将其替换为SQL Server中的JOIN

    游标sql server Relational database management systems including SQL Server are very good at processing ...

  4. 索引sql server_SQL Server索引操作

    索引sql server In the previous articles of this series, we described the structure of the SQL Server t ...

  5. 索引sql server_SQL Server索引–系列介绍

    索引sql server 描述 (Description) In this series, we will dive deeply in the SQL Server Indexing field, ...

  6. 索引sql server_SQL Server索引结构和概念

    索引sql server In my previous article, SQL Server Table Structure Overview, we described, in detail, t ...

  7. 索引sql server_SQL Server索引设计基础和准则

    索引sql server In the previous article of this series, SQL Server Index Structure and Concepts, we des ...

  8. 索引sql server_SQL Server报告– SQL Server索引利用率

    索引sql server Understanding indexing needs allows us to ensure that important processes run efficient ...

  9. 索引sql server_SQL Server索引设计的五个主要注意事项

    索引sql server In this article, we will discuss the most important points that we should consider when ...

最新文章

  1. JS同时上传表单图片和表单信息并把上传信息存入数据库,带php后端源码
  2. 轻量级 memcached缓存代理 twemproxy实践
  3. laravel实现数据库读写分离配置或者多读写分离配置
  4. Java调用C++webservice接口
  5. Appium查找元素
  6. 以IP段作为监听地址
  7. android点滴27:R文件无法加载 R cannot be resolved to a v...
  8. python中设置时间格式--模块datetime中日期和时间格式的参数
  9. 极简风海报作品合集|过目不忘的海报大片,越看越过瘾
  10. Struts2框架学习(二) Action
  11. 大麦盒子 Android4.4,大麦盒子DM4036机顶盒刷安卓系统教程
  12. html5添加上下居中,详解HTML5中垂直上下居中的解决方案
  13. 泡泡龙游戏开发系列教程(五)
  14. 6.Cython使用WinGw编译pyd
  15. 进口十大旋转编码器厂商
  16. 前端的图片优化的6种方案
  17. 04_Python简答题
  18. 代码随想录算法训练营第一天 704 二分查找、27 移除元素
  19. 文本匹配模型ESIM
  20. 邮箱大师支持html,网易邮箱大师

热门文章

  1. 图片转可编辑ppt_电脑如何简单快速将图片转为文字,不用下载任何软件,免费使用。...
  2. 安卓获取浏览器上网记录_在android中获取浏览器历史记录和搜索结果
  3. python二叉树最大深度的计算_Python学习笔记24(二叉树遍历、最大深度、最大宽度)...
  4. python3图像处理_Python3与OpenCV3.3 图像处理(二)--图像基本操作
  5. AGC 012 B - Splatter Painting
  6. 从零开始搭建系统2.4——Jenkins安装及配置
  7. Type 1120: Access of undefined property JSON 无法明确解析多名称引用 JSON
  8. TypeScript泛型接口
  9. 每日长难句打卡Day23
  10. (node:2612) DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead.