SQL Server 游标

      结果集,结果集就是select查询之后返回的所有行数据的集合。

      在关系数据库中,我们对于查询的思考是面向集合的。而游标打破了这一规则,游标使得我们思考方式变为逐行进行。

正常面向集合的思维方式是:

2

而对于游标来说:

3

      同样的,在性能上,游标会吃更多的内存,减少可用的并发,占用宽带,锁定资源,当然还有更多的代码量……

 

游标的分类

根据游标检测结果集变化的能力和消耗资源的情况不同,SQL Server支持的API服务器游标分为一下4种:

静态游标

  静态游标的结果集,在游标打开的时候建立在TempDB中,不论你在操作游标的时候,如何操作数据库,游标中的数据集都不会变。

  例如你在游标打开的时候,对游标查询的数据表数据进行增删改,操作之后,静态游标中select的数据依旧显示的为没有操作之前的数据。

  如果想与操作之后的数据一致,则重新关闭打开游标即可。

动态游标

  这个则与静态游标相对,滚动游标时,动态游标反应结果集中的所有更改。

  结果集中的行数据值、顺序和成员在每次提取时都会变化。

  所有用户做的增删改语句通过游标均可见。

  如果使用API函数或T-SQL Where Current of子句通过游标进行更新,他们将立即可见。

  在游标外部所做的更新直到提交时才可见。

只进游标

  只进游标不支持滚动,只支持从头到尾顺序提取数据,数据库执行增删改,在提取时是可见的,但由于该游标只能进不能向后滚动,所以在行提取后对行做增删改是不可见的。

键集驱动游标

  打开键集驱动游标时,该游标中的各个成员身份和顺序是固定的。

  打开游标时,结果集这些行数据被一组唯一标识符标识,被标识的列做删改时,用户滚动游标是可见的,如果没被标识的列增该,则不可见,比如insert一条数据,是不可见的,若可见,须关闭重新打开游标。

 

静态游标在滚动时检测不到表数据变化,但消耗的资源相对很少。

动态游标在滚动时能检测到所有表数据变化,但消耗的资源却较多。

键集驱动游标则处于他们中间,所以根据需求建立适合自己的游标,避免资源浪费。

 

T-SQL中游标的生命周期以及实现

      在T-SQL中,游标的生命周期由5部分组成:声明游标、打开游标、读取游标数据、关闭游标、释放游标。

1. 定义一个游标

      在T-SQL中,定义一个游标可以是非常简单,也可以相对复杂,取决于游标的参数。而游标的参数设置取决于你对游标原理的了解程度。

      游标其实可以理解成一个定义在特定数据集上的指针,我们可以控制这个指针遍历数据集,或者仅仅是指向特定的行,所以游标是定义在以Select开始的数据集上的:

4

T-SQL中的游标定义在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 ] ] ]
[;]

看起来很让人头痛是吧,下面仔细讲一下如何定义游标:

      游标分为游标类型和游标变量。

      对于游标变量来说,遵循T-SQL变量的定义方法。游标变量支持两种方式赋值,定义时赋值和先定义后赋值。

      定义游标变量像定义其他局部变量一样,在游标前加”@”,注意,如果定义全局的游标,只支持定义时直接赋值,并且不能在游标名称前面加“@”,两种定义方式如下:

5

下面我们来看游标定义的参数:

LOCAL 和 GLOBAL 二选一

LOCAL

  作用域为局部,只在定义它的批处理,存储过程或触发器中有效。

GLOBAL

  作用域为全局,由连接执行的任何存储过程或批处理中,都可以引用该游标。

  如果 GLOBAL 和 LOCAL 参数都未指定,则默认值由 default to local cursor 数据库选项的设置控制。

例如:

6

 

FORWARD_ONLY 和 SCROLL 二选一

FORWARD_ONLY

  指定游标只能从第一行滚动到最后一行。 

  FETCH NEXT 是唯一支持的提取选项。 

  如果在指定 FORWARD_ONLY 时不指定 STATIC、KEYSET 和 DYNAMIC 关键字,则游标作为 DYNAMIC 游标进行操作。 

  如果 FORWARD_ONLY 和 SCROLL 均未指定,则除非指定 STATIC、KEYSET 或 DYNAMIC 关键字,否则默认为 FORWARD_ONLY。 

  STATIC、KEYSET 和 DYNAMIC 游标默认为 SCROLL。 

SCROLL

  支持游标在定义的数据集中向任何方向,或任何位置移动。

如下图:

7

 

STATIC KEYSET DYNAMIC 和 FAST_FORWARD 四选一

      这四个关键字是游标所在数据集所反应的表内数据和游标读取出的数据的关系。

STATIC

  定义一个游标,以创建将由该游标使用的数据的临时复本。 对游标的所有请求都从 tempdb 中的这一临时表中得到应答;因此,在对该游标进行提取操作时返回的数据中不反映对基表所做的修改,并且该游标不允许修改。

DYNAMIC

  定义一个游标,以反映在滚动游标时对结果集内的各行所做的所有数据更改。

  行的数据值、顺序和成员身份在每次提取时都会更改。

  动态游标不支持 ABSOLUTE 提取选项。

KEYSET

  指定当游标打开时,游标中行的成员身份和顺序已经固定。

  对行进行唯一标识的键集内置在 tempdb 内一个称为 keyset 的表中。

  对基表中的非键值所做的更改(由游标所有者更改或由其他用户提交)可以在用户滚动游标时看到。

  其他用户执行的插入是不可见的(不能通过 Transact-SQL 服务器游标执行插入)。 

  如果删除某一行,则在尝试提取该行时返回值为 -2 的 @@FETCH_STATUS。 

  从游标外部更新键值类似于删除旧行后再插入新行。 具有新值的行不可见,且尝试提取具有旧值的行时返回的 @@FETCH_STATUS 为 -2。

  如果通过指定 WHERE CURRENT OF 子句来通过游标执行更新,则新值可见。

  注意:如果查询引用了至少一个无唯一索引的表,则键集游标将转换为静态游标。

FAST_FORWARD

  指定启用了性能优化的 FORWARD_ONLY、READ_ONLY 游标。 如果指定了 SCROLL 或 FOR_UPDATE,则不能也指定 FAST_FORWARD。

  注意:FAST_FORWARD 和 FORWARD_ONLY 可以同时用在同一个 DECLARE CURSOR 语句中。

 

READ_ONLY SCROLL_LOCKS OPTIMISTIC 三选一 
READ_ONLY

  禁止通过该游标进行更新。 在 UPDATE 或 DELETE 语句的 WHERE CURRENT OF 子句中不能引用该游标。 该选项优于要更新的游标的默认功能。

SCROLL_LOCKS

  指定通过游标进行的定位更新或删除一定会成功。 

  将行读入游标时 SQL Server 将锁定这些行,以确保随后可对它们进行修改。

  如果还指定了 FAST_FORWARD 或 STATIC,则不能指定 SCROLL_LOCKS。

OPTIMISTIC

  指定如果行自读入游标以来已得到更新,则通过游标进行的定位更新或定位删除不成功。 

  当将行读入游标时,SQL Server 不锁定行。 

  它改用 timestamp 列值的比较结果来确定行读入游标后是否发生了修改,如果表不含 timestamp 列,它改用校验和值进行确定。 如果已修改该行,则尝试进行的定位更新或删除将失败。 

  如果还指定了 FAST_FORWARD,则不能指定 OPTIMISTIC。

 

2. 打开游标

语法

OPEN { { [ GLOBAL ] cursor_name } | cursor_variable_name }

参数

GLOBAL

  指定 cursor_name 是指全局游标。

cursor_name

  已声明的游标的名称。

  如果全局游标和局部游标都使用 cursor_name 作为其名称,那么如果指定了 GLOBAL,则 cursor_name指的是全局游标;否则 cursor_name 指的是局部游标。

cursor_variable_name

  游标变量的名称,该变量引用一个游标。

 

3. 使用游标

      游标的使用分为两部分,一部分是操作游标在数据集内的指向,另一部分是将游标所指向的行的部分或全部内容进行操作。

语法

FETCH 
          [ [ NEXT | PRIOR | FIRST | LAST 
                    | ABSOLUTE { n | @nvar } 
                    | RELATIVE { n | @nvar } 
               ] 
               FROM 
          ] 
{ { [ GLOBAL ] cursor_name } | @cursor_variable_name } 
[ INTO @variable_name [ ,...n ] ] 

 

参数

只支持6种移动选项:

FIRST

  结果集的第一行。

LAST

  结果集的最后一行。

NEXT

  当前位置的下一行。

PRIOR

  当前位置的上一行。

ABSOLUTE(n)

  从游标的第一行开始数,直接跳到第n行。

  如果n为正整数,则读取第n条记录

  如果n为负数,则倒数提取第n条记录

  如果n为0,则不读取任何记录

RELATIVE(n)

  相对于当前位置数,跳第n行。

  如果n为正整数,则读取上次读取记录之后第n条记录

  如果n为负数,则读取上次读取记录之前第n条记录

  如果n为0,则读取上次读取的记录

 

提取数据

例如:

fetch first from orderNum_02_cursor
fetch relative 3 from orderNum_02_cursor
fetch next from orderNum_02_cursor
fetch absolute 4 from orderNum_02_cursor
fetch next from orderNum_02_cursor
fetch last from orderNum_02_cursor 
fetch prior from orderNum_02_cursor
select * from bigorder where orderNum='ZEORD003402'

 

提取数据赋值给变量

declare @OrderId int
fetch absolute 3 from orderNum_02_cursor into @OrderId
select @OrderId as id
select * from bigorder where orderNum='ZEORD003402'

 

 

 @@FETCH_STATUS

      通过检测全局变量@@Fetch_Status的值,获得提取状态信息,该状态用于判断Fetch语句返回数据的有效性。

      当执行一条Fetch语句之后,@@Fetch_Status可能出现3种值:

         0:Fetch语句成功。

        -1:Fetch语句失败或行不在结果集中。

        -2:提取的行不存在。

 

      这个状态值可以帮你判断提取数据的成功与否。

      游标经常会和全局变量@@FETCH_STATUS 与 WHILE 循环来共同使用,以达到遍历游标所在数据集的目的,例如:

declare @OrderId int
fetch absolute 3 from orderNum_02_cursor into @OrderId
while @@fetch_status=0  --提取成功,进行下一条数据的提取操作
 begin
   select @OrderId as id
   fetch next from orderNum_02_cursor into @OrderId  --移动游标
 end 

 

利用游标更新删除数据

--游标修改当前数据语法
Update 基表名 Set 列名=[,...] Where Current of 游标名
--游标删除当前数据语法
Delete 基表名  Where Current of 游标名
--游标更新删除当前数据
--1.声明游标
declare orderNum_03_cursor cursor scroll
for select OrderId ,userId from bigorder where orderNum='ZEORD003402'
--2.打开游标
open orderNum_03_cursor
--3.声明游标提取数据所要存放的变量
declare @OrderId int ,@userId varchar(15)
--4.定位游标到哪一行
fetch First from orderNum_03_cursor into @OrderId,@userId  --into的变量数量必须与游标查询结果集的列数相同
while @@fetch_status=0  --提取成功,进行下一条数据的提取操作 
 begin
   if @OrderId=122182
     begin
     Update bigorder Set UserId='123' Where Current of  orderNum_03_cursor  --修改当前行
     end
   if @OrderId=154074
      begin
      Delete bigorder Where Current of  orderNum_03_cursor  --删除当前行
      end
   fetch next from orderNum_03_cursor into @OrderId ,@userId  --移动游标
 end

 

4. 关闭游标

      游标打开后,服务器会专门为游标分配一定的内存空间存放游标操作的数据结果集,同时使用游标也会对某些数据进行封锁。所以游标一旦用过,应及时关闭,避免服务器资源浪费。

语法

CLOSE { { [ GLOBAL ] cursor_name } | cursor_variable_name }

 

参数

GLOBAL
  指定cursor_name 是指全局游标。

cursor_name
  打开的游标的名称。 如果全局游标和局部游标都使用 cursor_name 作为它们的名称,那么当指定 GLOBAL 时,cursor_name 指的是全局游标;其他情况下,cursor_name 指的是局部游标。

cursor_variable_name
  与打开的游标关联的游标变量的名称。

 

5. 释放游标

      删除游标,释放资源。

语法

DEALLOCATE { { [ GLOBAL ] cursor_name } | @cursor_variable_name }

 

参数

cursor_name
  已声明游标的名称。

  如果全局游标和局部游标都使用 cursor_name 作为它们的名称,那么指定 GLOBAL 时,cursor_name 指的是全局游标;未指定 GLOBAL 时,则指的是局部游标。

@cursor_variable_name
  cursor 变量的名称。

  @cursor_variable_name 的数据类型必须为 cursor。

 

对于游标一些优化建议:

      如果能不用游标,尽量不要使用游标

      用完用完之后一定要关闭和释放

      尽量不要在大量数据上定义游标

      尽量不要使用游标上更新数据

      尽量不要使用insensitive,static和keyset这些参数定义游标

      如果可以,尽量使用FAST_FORWARD关键字定义游标

      如果只对数据进行读取,当读取时只用到FETCH NEXT选项,则最好使用FORWARD_ONLY参数

 

总结

      本文从游标的基本概念,到生命周期来谈游标。游标是非常邪恶的一种存在,使用游标经常会比使用面向集合的方法慢2-3倍,当游标定义在大数据量时,这个比例还会增加。如果可能,尽量使用while,子查询,临时表,函数,表变量等来替代游标,记住,游标永远只是你最后无奈之下的选择,而不是首选。

     游标是邪恶的!

posted @ 2016-09-14 12:42  KUYUTI  阅读(176)  评论(0编辑  收藏  举报