If you create a table on Adaptive Server, but do not create a clustered index, the table is stored as a heap. The data rows are not stored in any particular order. This section describes how select, insert, delete, and update operations perform on heaps when there is no "useful" index to aid in retrieving data.
The phrase "no useful index" is important in describing the optimizer's decision to perform a table scan. Sometimes, an index exists on the columns named in a where clause, but the optimizer determines that it would be more costly to use the index than to perform a table scan.
Other chapters in this book describe how the optimizer costs queries using indexes and how you can get more information about why the optimizer makes these choices.
Table scans are always used when you select all rows in a table. The only exception is when the query includes only columns that are keys in a nonclustered index.
For more information, see "Index covering".
The following sections describe how Adaptive Server locates rows when a table has no useful index.
Lock schemes and differences between heaps
The data pages in an allpages-locked table are linked into a doubly-linked list of pages by pointers on each page. Pages in data-only-locked tables are not linked into a page chain.
In an allpages-locked table, each page stores a pointer to the next page in the chain and to the previous page in the chain. When new pages need to be inserted, the pointers on the two adjacent pages change to point to the new page. When Adaptive Server scans an allpages-locked table, it reads the pages in order, following these page pointers.
Pages are also doubly-linked at each index level of allpages-locked tables, and the leaf level of indexes on data-only-locked tables. If an allpages-locked table is partitioned, there is one page chain for each partition.
Another difference between allpages-locked tables and data-only-locked tables is that data-only-locked tables use fixed row IDs. This means that row IDs (a combination of the page number and the row number on the page) do not change in a data-only-locked table during normal query processing.
Row IDs change only when one of the operations that require data-row copying is performed, for example, during reorg rebuild or while creating a clustered index.
For information on how fixed row IDs affect heap operations, see "Deleting from a data-only locked heap table" and "Data-only-locked heap tables".
Select operations on heaps
When you issue a select query on a heap, and there is no useful nonclustered index, Adaptive Server must scan every data page in the table to find every row that satisfies the conditions in the query. There may be one row, many rows, or no rows that match.
Allpages-locked heap tables
For allpages-locked tables, Adaptive Server reads the first column in sysindexes for the table, reads the first page into cache, and follows the next page pointers until it finds the last page of the table.
Data-only locked heap tables
Since the pages of data-only-locked tables are not linked in a page chain, a select query on a heap table uses the table's OAM and the allocation pages to locate all the rows in the table. The OAM page points to the allocation pages, which point to the extents and pages for the table.
Inserting data into an allpages-locked heap table
When you insert data into an allpages-locked heap table, the data row is always added to the last page of the table. If there is no clustered index on a table, and the table is not partitioned, the sysindexes.root entry for the heap table stores a pointer to the last page of the heap to locate the page where the data needs to be inserted.
If the last page is full, a new page is allocated in the current extent and linked onto the chain. If the extent is full, Adaptive Server looks for empty pages on other extents being used by the table. If no pages are available, a new extent is allocated to the table.
Conflicts during heap inserts
One of the severe performance limits on heap tables that use allpages locking is that the page must be locked when the row is added, and that lock is held until the transaction completes. If many users are trying to insert into an allpages-locked heap table at the same time, each insert must wait for the preceding transaction to complete.
This problem of last-page conflicts on heaps is true for:
Single row inserts using insert
Multiple row inserts using select into or insert...select, or several insert statements in a batch
Bulk copy into the table
Some workarounds for last-page conflicts on heaps include:
Switching to datapages or datarows locking
Creating a clustered index that directs the inserts to different pages
Partitioning the table, which creates multiple insert points for the table, giving you multiple "last pages" in an allpages-locked table
Other guidelines that apply to all transactions where there may be lock conflicts include:
Keeping transactions short
Avoiding network activity and user interaction whenever possible, once a transaction acquires locks
Inserting data into a data-only-locked heap table
When users insert data into a data-only-locked heap table, Adaptive Server tracks page numbers where the inserts have recently occurred, and keeps the page number as a hint for future tasks that need space. Subsequent inserts to the table are directed to one of these pages. If the page is full, Adaptive Server allocates a new page and replaces the old hint with the new page number.
Blocking while many users are simultaneously inserting data is much less likely to occur during inserts to data-only-locked heap tables. When blocking occurs, Adaptive Server allocates a small number of empty pages and directs new inserts to those pages using these newly allocated pages as hints.
For datarows-locked tables, blocking occurs only while the actual changes to the data page are being written; although row locks are held for the duration of the transaction, other rows can be inserted on the page. The row-level locks allow multiple transaction to hold locks on the page.
There may be slight blocking on data-only-locked tables, because Adaptive Server allows a small amount of blocking after many pages have just been allocated, so that the newly allocated pages are filled before additional pages are allocated.
If conflicts occur during heap inserts
Conflicts during inserts to heap tables are greatly reduced for data-only-locked tables, but can still take place. If these conflicts slow inserts, some workarounds can be used, including:
Switching to datarows locking, if the table uses datapages locking
Using a clustered index to spread data inserts
Partitioning the table, which provides additional hints and allows new pages to be allocated on each partition when blocking takes place
Deleting data from a heap table
When you delete rows from a heap table, and there is no useful index, Adaptive Server scans the data rows in the table to find the rows to delete. It has no way of knowing how many rows match the conditions in the query without examining every row.
Deleting from an allpages-locked heap table
When a data row is deleted from a page in an allpages-locked table, the rows that follow it on the page move up so that the data on the page remains contiguous.
Deleting from a data-only locked heap table
When you delete rows from a data-only-locked heap table, a table scan is required if there is no useful index. The OAM and allocation pages are used to locate the pages.
The space on the page is not recovered immediately. Rows in data-only-locked tables must maintain fixed row IDs, and need to be reinserted in the same place if the transaction is rolled back.
After a delete transaction completes, one of the following processes shifts rows on the page to make the space usage contiguous:
The housekeeper process
An insert that needs to find space on the page
The reorg reclaim_space command
Deleting the last row on a page
If you delete the last row on a page, the page is deallocated. If other pages on the extent are still in use by the table, the page can be used again by the table when a page is needed.
If all other pages on the extent are empty, the entire extent is deallocated. It can be allocated to other objects in the database. The first data page for a table or an index is never deallocated.
Updating data on a heap table
Like other operations on heaps, an update that has no useful index on the columns in the where clause performs a table scan to locate the rows that need to be changed.
Allpages-locked heap tables
Updates on allpages-locked heap tables can be performed in several ways:
If the length of the row does not change, the updated row replaces the existing row, and no data moves on the page.
If the length of the row changes, and there is enough free space on the page, the row remains in the same place on the page, but other rows move up or down to keep the rows contiguous on the page.
The row offset pointers at the end of the page are adjusted to point to the changed row locations.
If the row does not fit on the page, the row is deleted from its current page, and the "new" row is inserted on the last page of the table.
This type of update can cause a conflict on the last page of the heap, just as inserts do. If there are any nonclustered indexes on the table, all index references to the row need to be updated.
Data-only-locked heap tables
One of the requirements for data-only-locked tables is that the row ID of a data row never changes (except during intentional rebuilds of the table). Therefore, updates to data-only-locked tables can be performed by the first two methods described above, as long as the row fits on the page.
But when a row in a data-only-locked table is updated so that it no longer fits on the page, a process called row forwarding performs the following steps:
The row is inserted onto a different page, and
A pointer to the row ID on the new page is stored in the original location for the row.
Indexes do not need to be modified when rows are forwarded. All indexes still point to the original row ID.
If the row needs to be forwarded a second time, the original location is updated to point to the new page--the forwarded row is never more than one hop away from its original location.
Row forwarding increases concurrency during update operations because indexes do not have to be updated. It can slow data retrieval, however, because a task needs to read the page at the original location and then read the page where the forwarded data is stored.
Forwarded rows can be cleared from a table using the reorg command.
For more information on updates, see "How update operations are performed".