SQL Server 2005では,「行レベルのバージョン管理(RLV:Row Level Versioning)」と呼ばれる,変更中のデータに対して同時アクセスを可能にする機能が追加された。ただし,RLVを使用すると,tempdbデータベースの使用量が増加する。ここでは,SQL Server 2005の新しいツールを使ってtempdbの使用状況を監視し,RLVのコストを低減する方法について説明する。

 まずは,RLVが内部的に使用される状況を確認しておこう。内部的にRLVが使用されると,SQL Server 2000のデータベースをSQL Server 2005にアップグレードした直後,まだSQL Server 2005の新機能を全く使っていなくてもコストが発生する可能性がある。そのため,そうしたコストとtempdbの使用状況を監視するという管理要件が追加される。

トリガーとRLV

 トリガーはSQL Serverの初期バージョンから実装されている機能だ。SQL Server 2005より前のバージョンでは,履歴データ(またはバージョン管理されたデータ)を利用できる機能はトリガーだけであった。

 トリガーは,deletedとinsertedという二つの擬似テーブルにアクセスできる。トリガーの内部ではこれらの二つのテーブルに実際のテーブルと同じようにアクセスできるが,トリガーの外部でそれらのテーブルにアクセスすると「不明なオブジェクト・エラー(unknown object error)」が発生する。

 DELETEトリガーの場合,トリガーを発生させた操作によって削除されたすべての行のコピーがdeletedテーブルに格納される。INSERTトリガーの場合,トリガーを発生させた操作によって挿入されたすべての行のコピーがinsertedテーブルに格納される。UPDATEトリガーの場合は,すべての行の更新前バージョンのコピーがdeletedテーブルに,更新後バージョンのコピーがinsertedテーブルに,それぞれ格納される。

 SQL Server 2005より前のバージョンのSQL Serverでは,トランザクション・ログをスキャンして最新トランザクションに属するすべてのログ・レコードを取り出すことによって,これらの擬似テーブルに格納する行を特定していた。insertedテーブルまたはdeletedテーブルには,トリガーが関連付けられているテーブルに挿入されたデータ,またはそのテーブルから削除されたデータが記録されているログ・レコードが格納されていた。

 SQL Server 2005では,これらの擬似テーブルはRLVテクノロジーを使って作成される。トリガーが関連付けられているテーブルに対してデータ変更操作を実行すると,SQL Serverはデータの古いバージョンと新しいバージョンを作成して,tempdbの中のバージョン・ストアに格納する。この動作は,いずれかのスナップショット分離レベルが有効かどうかに関係なく実行される。

 SQL Server 2005では,トリガーでdeletedテーブルにアクセスすると,データはバージョン・ストアから読み込まれる。トリガーがテーブルのどの行が新しい行かを判断するためにinsertedテーブルにアクセスする必要がある場合も,insertedテーブルの行はバージョン・ストアから読み込まれる。

 バージョン・ストア内のinserted行とdeleted行の編成は,スナップショット分離レベルによってバージョン管理された行の場合とは異なる。SQL Serverが行レベルのバージョン管理が必要と判断すると,バージョン管理の対象になる各行に14バイトが追加される。この14バイトは行ポインタとして使われ,実際のデータページ内の行がバージョン・ストア内の行を参照したり,バージョン・ストア内の行が別の行を参照するのに用いられる。

 スナップショット分離を使う場合は,各行は同じ行の一つ古いバージョンを参照する。ポインタにNULLが設定されている行が最も古いバージョンである。トリガーを使う場合は,ポインタの使い方は多少異なっている。ベーステーブルで変更された各行は,その行の削除されたバージョンを参照するが,削除された行は削除された別の行を参照することによってdeletedテーブルのすべての行がリンクされた状態を作り出す。同様にして,insertedテーブルのすべての行もリンクされた状態になる。

 バージョンストアが使われているかどうかは,データベースで行レベルのバージョン管理が有効になっていない場合でも確認できる。リスト1のコードは,AdventureWorksデータベースのHumanResources.Departmentテーブルのコピーを作成し,コピー先のテーブルに二つのトリガーを作成する(コードの一部はスペースの制約のため折り返され,複数行で表示されていることに注意)。

リスト1●テーブルのコピーと二つのトリガーを作成する

-- Turn off the snapshot options
ALTER DATABASE AdventureWorks
  SET ALLOW_SNAPSHOT_ISOLATION OFF
ALTER DATABASE AdventureWorks
  SET READ_COMMITTED_SNAPSHOT OFF
GO
-- Make a copy of the Department table (15 rows)
USE AdventureWorks
SELECT *
INTO Department
FROM  HumanResources.Department
GO
-- Create two triggers, one for UPDATE and
-- one for DELETE
CREATE TRIGGER upd_Department
ON Department
FOR UPDATE
AS
SELECT count(*) AS NumRows, 
    (sum(record_length_first_part_in_bytes)  + 
     sum(record_length_second_part_in_bytes))/8060.
       AS Version_store_Pages 
FROM  sys.dm_tran_version_store
GO
CREATE TRIGGER del_Department
ON Department
FOR DELETE
AS
SELECT count(*) AS NumRows, 
    (sum(record_length_first_part_in_bytes)  + 
     sum(record_length_second_part_in_bytes))/8060.
       AS Version_store_Pages 
FROM  sys.dm_tran_version_store
GO

 このトリガーは,バージョンストア内の行数とすべての行バージョンのサイズだけを返す。このスクリプトには,AdventureWorksデータベースの行のバージョン管理を無効にするステートメントが含まれる。このステートメントによって,データベースのオプションに関係なく,トリガーによるバージョンストアの使用状況を確実に取得できる。

 ここでDepartmentテーブルを1行更新して,バージョンストアの行数をチェックしてみよう。insertedテーブルが1行,deletedテーブルが1行と表示されるはずである。

UPDATE dbo.Department 
SET ModifiedDate = getdate() 
WHERE DepartmentID = 11; 

 次に,Departmentテーブルから1行削除して,バージョンストアの行数をチェックしてみよう。

DELETE dbo.Department 
WHERE DepartmentID = 12 

 今度は,deletedテーブルが1行だけ表示される。ただし,バージョン・クリーンアップ・スレッドがこのDELETEステートメントの実行前に完了しなかった場合は,前のUPDATEステートメントで生成された2行がバージョン・ストアに残っているので,DELETEステートメントで生成された1行と合わせて合計3行が表示される。