In relational databases, our thinking about queries is set-oriented. The cursor breaks this rule, and the cursor makes our way of thinking become line by line. For developers of class C, this way of thinking will be more comfortable.
The normal set-oriented way of thinking is:
And for cursors:
This is why cursors are evil. It makes developers lazy, too lazy to think about implementing certain functions with collection-oriented queries.
Similarly, in terms of performance, the cursor will eat more memory, reduce the available concurrency, occupy bandwidth, lock resources, and of course, more code ...
From the way the cursor reads the database, it is not difficult to see why the cursor takes up more resources, for example:
When you withdraw money from ATM, is it more efficient to withdraw 1000 at a time, or to withdraw 100 100 times?
Since cursors are so "evil", why study cursors
I personally think that existence is both reasonable. In conclusion, the reasons for learning cursors can be summarized as the following 2 points
1. The existing system has some cursors, and our query must be realized through the cursors
2. As an alternative way, when we exhausted while loops, subqueries, temporary tables, table variables, self-built functions or other ways to throw some queries, use cursors to achieve.
The life cycle and realization of cursor in T-SQL
In T-SQL, the life cycle of a cursor consists of 5 parts
1. Define a cursor
In T-SQL, defining a cursor can be very simple or relatively complex, depending on the parameters of the cursor. The parameter setting of the cursor depends on your understanding of the principles of the cursor.
The cursor can actually be understood as a pointer defined on a specific data set . We can control this pointer to traverse the data set , or just point to a specific row, so the cursor is defined on the data set starting with Select:
The cursor definition in T-SQL is as follows in MSDN:
DECLARE cursor_name CURSOR [ LOCAL | GLOBAL ] [ FORWARD_ONLY | SCROLL ] [ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ] [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ] [ TYPE_WARNING ] FOR select_statement [ FOR UPDATE [ OF column_name [ ,...n ] ] ] [;]
It seems to be a headache, right. Let's talk about how to define a cursor:
Cursors are divided into cursor types and cursor variables. For cursor variables, follow the T-SQL variable definition method (what, do n’t know the rules of T-SQL variable definition? Refer to my previous blog post). Cursor variables support assignment in two ways , When assigning and assigning after defining, define the cursor variable just like other local variables, add “@” before the cursor. Note that if you define a global cursor, you can only assign directly when defining the cursor, and you cannot add it before the cursor name. "@", Two definitions are as follows:
Let us look at the parameters defined by the cursor:
Choose one of LOCAL and GLOBAL
LOCAL意味着游标的生存周期只在批处理或函数或存储过程中可见,而GLOBAL意味着游标对于特定连接作为上下文,全局内有效,例如:
如果不指定游标作用域,默认作用域为GLOBAL
FORWARD_ONLY 和 SCROLL 二选一
FORWARD_ONLY意味着游标只能从数据集开始向数据集结束的方向读取,FETCH NEXT是唯一的选项,而SCROLL支持游标在定义的数据集中向任何方向,或任何位置移动,如下图:
STATIC KEYSET DYNAMIC 和 FAST_FORWARD 四选一
这四个关键字是游标所在数据集所反应的表内数据和游标读取出的数据的关系
STATIC意味着,当游标被建立时,将会创建FOR后面的SELECT语句所包含数据集的副本存入tempdb数据库中,任何对于底层表内数据的更改不会影响到游标的内容.
DYNAMIC是和STATIC完全相反的选项,当底层数据库更改时,游标的内容也随之得到反映,在下一次fetch中,数据内容会随之改变
KEYSET可以理解为介于STATIC和DYNAMIC的折中方案。将游标所在结果集的唯一能确定每一行的主键存入tempdb,当结果集中任何行改变或者删除时,@@FETCH_STATUS会为-2,KEYSET无法探测新加入的数据
FAST_FORWARD可以理解成FORWARD_ONLY的优化版本.FORWARD_ONLY执行的是静态计划,而FAST_FORWARD是根据情况进行选择采用动态计划还是静态计划,大多数情况下FAST_FORWARD要比FORWARD_ONLY性能略好.
READ_ONLY SCROLL_LOCKS OPTIMISTIC 三选一
READ_ONLY意味着声明的游标只能读取数据,游标不能做任何更新操作
SCROLL_LOCKS是另一种极端,将读入游标的所有数据进行锁定,防止其他程序进行更改,以确保更新的绝对成功
OPTIMISTIC是相对比较好的一个选择,OPTIMISTIC不锁定任何数据,当需要在游标中更新数据时,如果底层表数据更新,则游标内数据更新不成功,如果,底层表数据未更新,则游标内表数据可以更新
2.打开游标
当定义完游标后,游标需要打开后使用,只有简单一行代码:
OPEN test_Cursor
注意,当全局游标和局部游标变量重名时,默认会打开局部变量游标
3.使用游标
游标的使用分为两部分,一部分是操作游标在数据集内的指向,另一部分是将游标所指向的行的部分或全部内容进行操作
只有支持6种移动选项,分别为到第一行(FIRST),最后一行(LAST),下一行(NEXT),上一行(PRIOR),直接跳到某行(ABSOLUTE(n)),相对于目前跳几行(RELATIVE(n)),例如:
对于未指定SCROLL选项的游标来说,只支持NEXT取值.
第一步操作完成后,就通过INTO关键字将这行的值传入局部变量:
比如下面代码:
游标经常会和全局变量@@FETCH_STATUS与WHILE循环来共同使用,以达到遍历游标所在数据集的目的,例如:
4.关闭游标
在游标使用完之后,一定要记得关闭,只需要一行代码:CLOSE+游标名称
CLOSE test_Cursor
5.释放游标
当游标不再需要被使用后,释放游标,只需要一行代码:DEALLOCATE+游标名称
DEALLOCATE test_Cursor
对于游标一些优化建议
- 如果能不用游标,尽量不要使用游标
- 用完用完之后一定要关闭和释放
- 尽量不要在大量数据上定义游标
- 尽量不要使用游标上更新数据
- Try not to use insensitive, static and keyset parameters to define cursors
- If possible, use the FAST_FORWARD keyword to define the cursor
- If you only read the data and only use the FETCH NEXT option when reading, it is best to use the FORWARD_ONLY parameter
to sum up
This article discusses cursors from the basic concept of cursors to life cycle. Cursors are a very evil existence. The use of cursors is often 2-3 times slower than the use of collection-oriented methods. When the cursor is defined in a large amount of data, this proportion will increase. If possible, try to use while, subqueries, temporary tables, functions, table variables, etc. to replace the cursor, remember that the cursor is always your last choice but not the first choice.
Cursors are evil!