一
1. 什幺是光标?
光标,也有人称为游标。概括的讲,它是基于记录的。
过去,关系型数据库没有象现在这样被广泛的应用。那时候,人们大多使用 dBase 这样的小型数据库软件。这类数据库确切的说应为数据文件管理软件。他们是面向记录的。
不过,这种方式也许更符合人们的习惯。比如,我们在电话本中查找号码,在学生档案中查找档案,最终都要归结于其中的一个号码,一个档案,那就是一条记录。现实生活中,我们在一张表格中寻找某一项时,可能会用手一条一条逐行的扫过,以帮助我们找到所需的那条记录。对应于数据库来说,这就是光标的模型。所以,你可以这样想象:表格是数据库中的表,而我们的手好比是光标。
所以,当你使用类似 .MoveNext,.MoveLast 这样的语句时,觉得再自然不过了。
现在,你明白什幺是光标了吧。光标就是数据的 ' 定位系统 ' 。
这个 ' 定位系统 ' 粗分有两种:服务器光标和客户光标。对应于 ADO 中的 CursorLocation 。举例来说:
Set rs = Server.CreateObject("ADODB.RecordSet")
rs.CursorLocation = adUseServer '缺省,使用服务器光标
.....
rs.CursorLocation = adUseClient '使用客户光标
2. 什幺是服务器光标?
或者称为 API 服务器光标。
假设你要查询有关编程语言的书,写成 SQL 语句就是:SELECT book_id,book_title FROM books WHERE book_catalog = '编程' ORDER BY book_title,同时你指定使用服务器光标。
这条语句发送到服务器,服务器对数据进行检索,将符合查询条件的记录集合放入临时表(对某些光标类型是这样)中。每当你进行 .MoveNext 操作,服务器就会发送一条记录到客户端的缓冲区,然后你才可以使用它。
3. 什幺是 CacheSize ?
继续上面的例子,假设符合查询条件的记录有100个,也就是说,用 .MoveNext 这种方法遍历该结果集需要同服务器交互100次。我们可以通过设置 CacheSize 使客户与服务器的通信变少。上面的例子其实就是 CacheSize=1 的情况,这是缺省值。
假设 CacheSize=4,当 RecordSet 对象打开时,服务器发送4条记录到客户端。前4次的 .MoveNext 操作实际上是在客户缓冲区中得到数据,当第5次 .Movenext 时,服务器才发送下一个4条记录。由此,减少了客户与服务器间的网络通信。
那幺是不是说 CacheSize 越大越好呢?千万不要想当然。万事都有两面性,CacheSize 也一样。客户端请求数据,服务器发送数据,这个过程有点象交通管理。CacheSize 过高,会阻塞交通,甚至引起数据丢失(比如当 Cachesize 大于客户端缓冲区时)。对于不同的应用,所取的值也不同。
另外要指出的是,使用任何形式的光标都不是最有效的访问数据的方法,Cachesize 有的时候并不是瓶颈,尽量将使用光标的程序转换为面向结果集的程序,性能会提高很多。
3. 什幺是客户光标?
既然光标是数据的 ' 定位系统 ',那幺在客户端也可以完成。
客户光标的产生由来已久,当初是为了弥补服务器的不足(有些数据库引擎就不支持光标)。随着服务器光标的出现,客户光标似乎已经过时了,不过还是那句话:万事都有两面性,在 internet 上,与数据库的连接并不是永久的,使用客户光标能使我们获得同使用服务器光标一样的功能。
当 CursorLoction 属性设成 adUseClient 时,微软的光标服务( Cursor Service )创建 RecordSet 对象,用前向 / 只读的光标方式从服务器将所有查询结果检索出来,并且存储在客户缓冲区中。当应用程序通过 ADO 请求数据时,光标服务就从客户缓冲区中检取数据。这种方式在连接远程服务器时非常有用,它会提高应用程序的性能。如果你访问的数据库是 Jet 数据库( Access ),而且在本地,那么用客户光标非但不提高性能,还会使性能下降。这时候,数据将被缓存两次,数据库一次,光标服务一次。
如果考虑应用的功能,客户光标功能是很完善的,它能支持某些数据库不能完成的操作( 视数据库的情况而定 )。
4. 什幺是 DisConnected RecordSet ?
我们使用了客户光标,就可以断开与数据库的连接,释放 Connection 对象。这样的结果集就是 DisConnected RecordSet。举例说明:
Dim c As New ADODB.Connection
Dim r As New ADODB.Recordset
On Error GoTo handler
c.ConnectionString = connectStr
c.CursorLocation = adUseClient
c.Open
Set r.ActiveConnection = c
r.Open SqlText, , adOpenKeyset, adLockBatchOptimistic, -1
Set r.ActiveConnection = Nothing ' This disconnects the recordset.
c.Close
Set c = Nothing
......
......
' Recordset is now in disconnected state; do something with it.
r.Close
Set r = Nothing
......
二
在光标基础(1)中,主要讲述了什么是光标,什么是服务器光标,什么是客户光标。我们还把光标比作手,它的作用就是定位某条记录。
光标没有手那样灵活,而且要复杂的多。根据不同的需要,光标被划分为静态光标( Static ),键集光标( KeySet),动态光标( Dynamic ),前向光标( Forward-only )。在文章最后还要介绍有关默认结果集的概念。
1. 静态光标
静态光标为查询结果生成一个临时的拷贝并存储在 tempdb 中,你对结果集的所有操作,其实质是在一个私有的,临时的数据拷贝上进行的,这个结果集的成员是固定的,别人对基表所作的任何修改都不会实时的反映出来。
采用静态光标在服务器中创建临时表是要付出代价的。我们知道 tempdb 经常用于复杂的数据检索,如果结果集太大就会影响数据查询的效率。
2. 键集光标
与静态光标不同的就是键集光标只在 tempdb 中存储唯一标识结果集各行的关键值 -- 键集(比如:索引)。
假设我们要查询这样的语句 "SELECT Cust_name FROM customers WHERE cust_id > 100",假定用户101,200,300符合查询条件。此后,每一次提取操作都会使用这些键值。换句话就是说,以后的 .Movenext 操作使服务器重新以键集为基础进行查找,即:"SELECT Cust_name FROM customers WHERE cust_id IN(101,200,300)"。即使其他人输入了用户400,它也不会出现在结果集中。而基于键集所作的修改是可以看到的。
对于键集光标,要求该光标用到的表都应有一个唯一索引,如果不满足这个条件,该光标会转换成静态光标。
3. 动态光标
在这种光标中,SELECT 语句在每次提取记录时都要执行一次。与键集光标不同,它执行的是原 SELECT 语句,即:"SELECT Cust_name FROM customers WHERE cust_id > 100"。这就意味着动态光标的成员是不固定的,你在光标内所作的修改,或者其他人所作的修改,都会在下一次提取时反映出来。
说句题外话,有好多人问:为什么RecordSet.RecordCount 返回 -1?(-1 代表不知道有多少条记录,)其实好好想一下,不难得出结论。静态光标和键集光标的成员是固定的,自然可以知道到底有多少条记录,而动态光标却相反,它不可能返回一个随时变化的值。
4. 前向光标
顾名思义,只向前滚动的光标。这是 ADO 的缺省光标,通常从头到尾进行数据处理,即只沿着一个方向,所以每一行不会被重新提取。前向光标是最快的光标。
5. 默认的结果集
默认结果集就是那些未以光标形式返回的结果集。
它的另一个名称是 'firehose' ,这个词更形象的反映了默认结果集的工作方式:只要客户端不断的处理返回的记录,使网络缓冲区有空间发送更多的结果,那么服务器就会向客户端继续发出记录。客户端不向服务器发出请求,服务器仅是不断的向网络发送数据。
尽管我们把默认结果集看作是前向 / 只读的光标,但这仅仅是一种称呼,它确实同真正的光标不一样。我们可以使用光标这个词,但应明确:从 SQL Server 的角度来看,默认结果集不是一个光标,它不涉及服务器的光标服务。
默认结果集的效率通常比光标高。