INF: Overview of SQL Server, ODBC, and DB-Library Cursors
ID: Q156489
|
The information in this article applies to:
-
Microsoft SQL Server version 6.0
SUMMARY
This article outlines the cursor models offered by SQL Server 6.0 server
cursors, the DB-Library Cursor Library, and Open Database Connectivity
(ODBC). Users who want a more in-depth discussion of cursors can read the
section on the ODBC Cursor Model in "Inside ODBC" by Kyle Geiger, available
from Microsoft Press in most bookstores.
MORE INFORMATION
All operations in a relational database operate on a complete set of rows.
For example, an UPDATE command works on the set of all rows that satisfy
the conditions in the UPDATE's where clause. However, there are times when
an application's work can be more easily implemented by performing a series
of operations based on subsets of rows in a result set, or by working
through a result set one row at a time. This is especially true for
interactive client-server applications that present result set rows to a
user who wants to scroll up and down the result set in blocks of rows at a time.
Cursors offer applications the ability to process through a result set one
row, or one block of rows, at a time. Think of a cursor as a pointer to a
current position, or a specific row, within the result set of a single
SELECT command. The following is a simple example of an application using a
cursor to retrieve all the rows that result from a SELECT:
DECLARE cursor_name CURSOR FOR select * from some_table
OPEN cursor_name
While there are rows to fetch
FETCH NEXT FROM cursor_name
CLOSE cursor_name
DEALLOCATE cursor_name
The real power of cursors is that the FETCH command can be used to scroll
around within the result set, not just going through one row at a time. The
following table shows what the application can do with different FETCH
commands:
Command Result
------------------------------------------------------------------------------
FETCH FIRST Positions the cursor on the first row
FETCH RELATIVE X Jumps the cursor X rows from the current position
FETCH ABSOLUTE Y Positions the cursor Y rows from the start of a
result set
FETCH PRIOR Reads the row just before the current row
Once positioned on a row in a cursor, an application can work with the data
from that row.
Some cursors also support UPDATE operations, and applications can perform
an UPDATE on the row where the cursor is currently positioned. In the case
of a cursor built using a SELECT that references multiple tables, the
UPDATE command can only reference one of the tables. Therefore, the
operation affects only the table actually named in the UPDATE command. For
example, in the following operation, only one row in some_table_b would be
deleted; no rows in some_table_a would be affected:
DECLARE cursor_name CURSOR FOR
select *
from some_table_a A, some_table_b B
where A.PrimaryKey = B.ForeignKey
OPEN cursor_name
FETCH FIRST FROM cursor_name
DELETE FROM some_table_b WHERE CURRENT OF cursor_name
CLOSE cursor_name
DEALLOCATE cursor_name
ODBC and DB-Library cursors are used by applications written in languages
such as C, C++, or Visual Basic using the ODBC or DB-Library APIs. The
server cursors introduced with SQL Server 6.0 are used in Transact-SQL
scripts and stored procedures. They are also used to implement the cursors
in ODBC and DB-Library applications. When either the SQL Server ODBC driver
or a DB-Library application is asked to open a cursor, they implement the
cursor by opening a server cursor of the same type. The ODBC or DB-Library
application is not aware that underneath the API, it is actually working
with server cursors.
The cursors defined in ANSI SQL only support operations that act upon one
row at a time. The cursors defined in DB-Library and ODBC extend this
functionality to support multiple row operations in cursors. ODBC does this
by introducing the concept of a rowset size. If an ODBC application sets
the rowset size option to 10, then each FETCH operation will retrieve 10
rows. DB-Library offers the same functionality through the nrows parameter
of dbcursoropen().
The remainder of this article describes the operation of DB-Library and
ODBC cursors; doing so requires defining all of the cursor options. After
describing these cursors and defining the principles, it will then be
easier to describe the simplified ANSI-style cursors available in Transact-
SQL.
Originally, Microsoft DB-Library did not use the concept of cursors. An
application ran a SELECT, and then read the rows back sequentially, one row
at a time in a forward direction, until the end of the result set was
reached. The result set did not support positioned updates; it was simply
the stream of rows that came back from the SELECT. If an application wanted
to UPDATE, INSERT, or DELETE one of the rows in the result set, it had to
build an UPDATE command targeted at the base table referenced by the
SELECT, and then reference the relevant key values in the where clause of
the UPDATE command.
The version of DB-Library that came with SQL Server 4.2 for OS/2 introduced
the DB-Library Cursor Library, which supported scrollable, updatable
cursors. Starting with SQL Server 6.0, the DB-Library Cursor Library
implemented its cursors by opening the server cursors introduced in SQL
Server 6.0. A DB-Library application would still either open a standard
result set, or use the Cursor Library if it wanted a scrollable, updatable
cursor.
DB-Library classifies its cursors into three categories. A client cursor is
one implemented on the client; these are the only cursors supported in SQL
Server 4.21a and earlier. These cursors can also be implemented in SQL
Server 6.0 and later, if the application sets the DBCLIENTCURSORS option.
An explicit server cursor is implemented under SQL Server 6.0 and later, if
the first FETCH is done using the extended function dbcursorfetchex(). A
transparent server cursor is implemented under SQL Server 6.0 and later if
the first FETCH is done using the standard dbcursorfetch(). There are some
differences between transparent and explicit cursors when fetching rows.
Refer to the "Programming DB-Library 6.0 for C" manual for the specific
distinctions.
The ODBC API does not make a distinction between a result set and a cursor.
In the ODBC model, all SQL commands return a result set, which an
application fetches through using SQLFetch() or SQLExtendedFetch(). Before
running a SQL command, an ODBC application can set statement options to
control the cursor behavior of the result set. These options and their
default settings are:
Option Default
--------------------------------------
SQL_CURSOR_TYPE SQL_FORWARD_ONLY
SQL_CONCURRENCY SQL_READ_ONLY
SQL_ROWSET_SIZE 1
These settings are identical to the characteristics of the default result
set in DB-Library. If an ODBC application wants cursor behavior, it simply
changes these options to the desired setting before running a SQL command.
If an ODBC application runs a command with the default settings, the
Microsoft SQL Server ODBC driver will process the result set the same way a
DB-Library application would process its standard result set. Prior to SQL
Server 6.0, this was the only option supported by the Microsoft SQL Server
ODBC driver, unless the application used the ODBC Cursor Library (see
Appendix G of the ODBC 2.0 Programmer's Reference for more information on
the ODBC Cursor Library). Starting with SQL Server 6.0, the Microsoft SQL
Server ODBC driver supports the other cursor options by using the server
cursors introduced with SQL Server 6.0. If any of these statement options
are changed from their defaults, the Microsoft SQL Server ODBC driver
implements the cursor functionality by opening a server cursor with the
same characteristics.
ODBC applications also do not typically perform positioned updates using
the WHERE CURRENT OF cursor_name syntax on UPDATE commands. Instead, ODBC
applications typically use the ODBC API function SQLSetPos() for positioned
updates. For more information, refer to the ODBC 2.0 Programmer's Guide.
Cursor Types
ODBC and DB-Library share the following five cursor types:
- STATIC Cursors
ODBC: SQL_CURSOR_TYPE = SQL_CURSOR_STATIC
DB-Library: scrollopt = CUR_INSENSITIVE
In a static cursor, the complete result set is built when the cursor is
opened, and the cursor will not reflect any changes made in the database
that may affect either the rows that make up the result set, or the
values in the columns of those rows. In other words, static cursors will
still show rows in the result set that have been deleted from the
database after the cursor was opened. If new rows have been inserted
that satisfy the conditions of the cursor's SELECT, they will not appear
in the cursor. If rows in the result set have been updated, the new data
values will not appear in the cursor. No UPDATE, INSERT, or DELETE
operations are reflected in a static cursor (unless the cursor is closed
and reopened), not even modifications made by the same user who opened
the cursor. Static cursors are read-only.
- DYNAMIC Cursors
ODBC: SQL_CURSOR_TYPE = SQL_CURSOR_DYNAMIC
DB-Library: scrollopt = CUR_DYNAMIC
Dynamic cursors are the opposite of static cursors; they reflect all
changes made to the rows in their result set as the user scrolls around
the cursor. In other words, the data values and membership of rows in
the cursor can change dynamically on each FETCH. This includes all
DELETE, INSERT, and UPDATE commands either made by the user who opened
the cursor or committed by other users. Dynamic cursors do not support
FETCH ABSOLUTE, because the size of the result set and the position of
rows within the result set are not constant. The row that starts out as
the tenth row in the result set may be the seventh row the next time a
FETCH is performed.
- FORWARD_ONLY Cursors
ODBC: SQL_CURSOR_TYPE = SQL_CURSOR_FORWARD_ONLY
DB-Library: scrollopt = CUR_FORWARD
This cursor is similar to a dynamic cursor, but it only supports
fetching the rows serially in sequence.
- KEYSET Cursors
ODBC: SQL_CURSOR_TYPE = SQL_CURSOR_KEYSET_DRIVEN
DB-Library: scrollopt = CUR_KEYSET
With a keyset cursor, the membership of rows in the result set and their
order is fixed when the cursor is opened. Changes to data values in
these rows (made by the current user or committed by other users) will
be reflected in the rows as the user scrolls through the cursor. Changes
that would affect either a row's membership or order in the result set,
such as deletions, insertions, or updates of the key value, are not
reflected unless the cursor is closed and reopened. Keyset cursors are
controlled through a set of unique identifiers (keys), known as the
keyset. The keyset is the set of all the key values that made up the
rows in the result set when the cursor was opened. The cursor will
reflect any changes made to columns that do not make up the keyset, but
it will not reflect any changes that would affect the order or
membership of the keyset.
- MIXED Cursors
ODBC: SQL_CURSOR_TYPE = SQL_CURSOR_KEYSET_DRIVEN, SQL_KEYSET_SIZE = n
DB-Library: scrollopt = n (where 1 < n < number of rows in cursor)
Mixed cursors combine features of keyset and dynamic cursors. With a
mixed cursor, the size of the keyset is less than the set of all the
keys in the result set. For example, a mixed cursor with a result set of
1,000 rows can have a keyset size of 100. As long as the user scrolls
within the same keyset, the cursor operates as a keyset cursor. However,
if the user scrolls beyond the bounds of the current keyset, the cursor
works as a dynamic cursor as it builds the new keyset. Once in the new
keyset, the cursor once again operates as a keyset cursor until the user
scrolls beyond the bounds of the new keyset.
All ODBC cursors support the concept of a rowset, which is the number of
rows returned on individual fetches. For example, if an application is
presenting a 10 row grid to the user, the cursor can be defined with a
rowset size of 10, to simplify mapping data into the grid. DB-Library
implements the same concept, using the fetch buffer size on its cursors.
Concurrency Options
In addition to the cursor types, cursor operations are also affected by the
concurrency options set by the application:
ODBC: SQL_CONCURRENCY = SQL_CONCUR_READONLY
DB-Library: concuropt = CUR_READONLY
With this option set, the cursor will not support UPDATE commands. Locks
are not held on the underlying rows that make up the result set.
ODBC: SQL_CONCURRENCY = SQL_CONCUR_VALUES
DB-Library: concuropt = CUR_OPTCCVAL
This is the first option offering optimistic concurrency control.
Optimistic concurrency control is a standard part of transaction control
theory, and is discussed in most papers and books on the subject. The user
or application chooses optimistic control when "optimistic" that there is a
slight chance that anyone else may have updated a row in the interval
between when the cursor is opened and when the row is updated. When the
cursor is opened in this mode, no locks are held on the underlying rows, in
order to maximize throughput. If the user attempts an UPDATE, SQL Server
compares the current values in the row with the values retrieved when the
cursor was opened. If any of the values have changed, the engine knows that
someone else has already updated the row, and it returns an error. If the
values are the same, the cursor engine performs the UPDATE. Selecting this
option causes the user or application to accept the responsibility of
dealing with an occasional error indicating that another user updated the
row and changed the values. A typical action taken by an application that
receives this error would be to refresh the cursor, to get the new values,
and then let the user decide if he or she still wants to perform the UPDATE
on the new values.
ODBC: SQL_CONCURRENCY = SQL_CONCUR_ROWVER
DB-Library: concuropt = CUR_OPTCC
This is the second optimistic concurrency control option, based on row
versioning. With row versioning, the underlying table must have a version
identifier of some type that the cursor engine can use to determine whether
the row has been changed since it was read into the cursor. In SQL Server,
this is the facility offered by the timestamp datatype. SQL Server
timestamps are binary numbers that indicate the relative sequence of
modifications in a database. Each database has a global current timestamp
value. Each time a page in the database is modified in any way, SQL Server
stores the current value in the page, and then increments it. If a table
has a timestamp column, then the timestamps are taken down to the row
level. Each time any row is modified, its timestamp column is updated with
the current timestamp.
The cursor engine can then compare a row's current timestamp value with the
timestamp value that was first retrieved into the cursor, to determine
whether the row has been updated. The engine does not have to compare the
values in all columns, only the timestamp. If an application requests
SQL_CONCUR_ROWVER (ODBC) or CUR_OPTCC (DB-Library) on a table that does not
have a timestamp column, the cursor will default to the values-based
optimistic concurrency control, SQL_CONCUR_VALUES or CUR_OPTCCVAL.
ODBC: SQL_CONCURRENCY = SQL_CONCUR_LOCK
DB-Library: concuropt = CUR_LOCKCC
This option implements pessimistic concurrency control, in which the
application attempts to lock the underlying database rows at the time they
are read into the cursor result set. For cursors using server cursors, an
update intent lock is placed on the row when it is read into the cursor. If
the cursor is opened within a transaction, these intent-to-update locks are
held until the transaction is terminated (committed or rolled back). If the
cursor has been opened outside a transaction, the lock is dropped
immediately after the row has been read into the cursor. Thus, applications
wanting full pessimistic concurrency control would typically open the
cursor within a transaction. An update intent lock prevents any other task
from acquiring an update intent or exclusive lock; this prevents any other
task from updating the row. An update intent lock, however, will not block
a shared lock, so it does not prevent other tasks from reading the row
unless the second task is also requesting a read with an update intent
lock.
Please note that in all of these concurrency options, when any row in the
cursor is updated, SQL Server will lock it with an exclusive lock. If the
update has been done within a transaction, the exclusive lock will be held
until the transaction is terminated. If the update has been done outside of
a transaction, the update is automatically committed when it is completed
and the exclusive lock is freed. Because SQL Server must acquire an
exclusive lock before it updates the row, updates done through a cursor
(just like standard updates) can be blocked by other connections holding a
share lock on the row.
Transaction Isolation Levels
The full locking behavior of cursors is based on an interaction between the
concurrency options discussed above and the transaction isolation level set
by the client. ODBC clients set the transaction isolation level by setting
the connection option SQL_TXN_ISOLATION. DB-Library applications and
Transact-SQL scripts set the isolation level by running the Transact-SQL
SET TRANSACTION ISOLATION LEVEL command. Users should combine the read
locking behaviors of the combination of the concurrency and transaction
isolation level options to determine the full locking behavior of a
specific cursor environment.
READ COMMITTED (The default for both SQL Server and ODBC)
ODBC: SQL_TXN_ISOLATION = SQL_TXN_READ_COMMITED
DB-Library: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SQL Server acquires a shared lock while reading a row into a cursor, but
frees the lock immediately after reading the row. Because a shared lock
request will be blocked by an exclusive lock, a cursor is prevented from
reading a row that another task has updated but not yet committed.
READ UNCOMMITTED
ODBC: SQL_TXN_ISOLATION = SQL_TXN_READ_UNCOMMITED
DB-Library: SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SQL Server requests no locks while reading a row into a cursor, and honors
no exclusive locks. This means that cursors can be populated with values
that have already been updated, but not yet committed. The user is
bypassing all of SQL Server's locking transaction control mechanisms.
REPEATABLE READ or SERIALIZABLE
ODBC: SQL_TXN_ISOLATION = SQL_TXN_REPEATABLE_READ_UNCOMMITED
or SQL_TXN_ISOLATION = SQL_TXN_SERIALIZABLE
DB-Library: SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
or SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
SQL Server still requests a shared lock on each row as it is read into the
cursor as in READ COMMITTED, but if the cursor is opened within a
transaction, the shared locks are held until the end of the transaction,
instead of being freed after the row is read. This is the same effect as
specifying HOLDLOCK on a select command.
Note that the ODBC API specifies additional transaction isolation levels,
but these are not supported by SQL Server or the SQL Server ODBC Driver.
Transact-SQL Cursor Types
The ODBC and DB-Library cursor types above offer extensions beyond the ANSI
cursor model, primarily to support the functions needed in interactive
applications. The server cursors in Transact-SQL are implemented to the
ANSI cursor standard, so they do not offer all the options of ODBC and
DB-Library cursors. Because Transact-SQL cursors are used in stored
procedures and triggers, they do not need the interactive extensions in
DB-Library and ODBC cursors.
The following characteristics of Transact-SQL cursors are set when the
cursor is declared:
DECLARE cursor_name [INSENSITIVE] [SCROLL] CURSOR
FOR select_statement
[FOR {READ ONLY | UPDATE [OF column_list]}]
The type of cursor implemented depends on the combination of SCROLL and
INSENSITIVE options specified.
DECLARE cursor_name INSENSITIVE CURSOR ...
The resulting cursor will be an insensitive, or static, cursor. It will
also be forward_only and read_only.
DECLARE cursor_name INSENSITIVE SCROLL CURSOR ...
The resulting cursor will be similar to the INSENSITIVE cursor; the only
difference is that it will be scrollable instead of forward_only.
DECLARE cursor_name SCROLL CURSOR ...
The resulting cursor will be implemented as a keyset cursor. If there is no
unique index on the underlying table the cursor will be read_only.
DECLARE cursor_name CURSOR ...
The resulting cursor will be implemented as a forward_only cursor.
In SQL Server 6.0, forward_only cursors default to keyset cursors. The
cursor will be created as INSENSITIVE (read_only) if any of the following
conditions are true:
- DISTINCT, UNION, GROUP BY, and/or HAVING are used.
- One or more of the underlying tables does not have a unique index.
- An outer join is used.
- A constant expression is included in the select_list.
In SQL Server 6.5, forward_only cursors default to dynamic cursors. If the
cursor's select clause has an ORDER BY, the cursor will be created as
INSENSITIVE, unless the underlying table has a unique index.
Additional query words:
dblib db-lib tsql t-sql tran-sql
Keywords : kbusage SSrvDB_Lib SSrvGen SSrvTran_SQL
Version : 6.0
Platform : WINDOWS
Issue type : kbhowto
Last Reviewed: April 2, 1999