* MS SQL Server で DB から SQL スクリプトを生成する手順とオプション
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [MS SQL Server] [SQL]
MS SQL Server2000 の 管理ツール Enterprise Manager で、既に存在するデータベースのインスタンスやテーブル、ストアドプロシージャなどをスクリプト化する手順と、指定するオプションのメモ。
要するに、GUI のツールで開発用サーバ上の DB を直接操作しながら作って、完成した時点でテストサーバや本番サーバに配備するというスタイルで DB まわりを作るようになったので、逆の作業が必要になったということだ。
もちろん、私も初めて DB を作ったときは CREATE TABLE をテキストエディタで手書きしたものだ。その後 CREATE TABLE を自動生成するスクリプトを作ったり、したが、現在は GUI のツールを利用するというスタイルに落ち着いている。私が扱うのはテーブル数15から30程度の小規模な DB なので、それで十分用が済んでしまう。
わざわざ SQL に落とさずに、SQL Server の DTS (Data Transformation Service - データ変換サービス) で転送すれば用が済んでしまう場合もある。ただ、プレーンテキストのスクリプトにするということは、スナップショットとして保存したり、Subversion や PVCS、VSS などの汎用のバージョン管理ツールで履歴を管理できるということだ。これは大きな利点だ。SQL Server は VisualStudio + VSS と緊密に連携することもできるが、サーバの構成などによってはその方法を採れないことも多いため、スクリプト化は非常にありがたい機能だ。
「SQL スクリプト生成ウインドウ」が表示されるので、対象オブジェクトやオプションを指定する。
以下、オプションのメモ。
■はチェックボックスのチェックオンを、□はオフを表す。
●はラジオボタンのチェックオンを、○はオフを表す。
スクリプト ファイルに説明用ヘッダーを含める(I)をオンにしておくと、対象オブジェクトの名前と現在の日付をコメントとして挿入してくれる。以下のような感じ。「/****** オブジェクト : ストアド プロシージャ dbo.P_GetLandscapeCustomers スクリプト日付 : 2006/08/23 12:43:12 ******/」テキストの差分をとる場合に邪魔になる場合はオフにすればいい。
「拡張プロパティを含める」をオンにしておくと、EnterpriseManager のテーブルデザイナで付加したカラム(列)への説明コメントも含めてスクリプトを生成してくれる。
- なぜ SQL スクリプトを作成するのか
既存の DB から SQL スクリプトを作成するという、本来と逆の手順をとるのはなぜか。それは、DB のテーブル設計やストアドのコーディングを RDBMS 付属の GUI ツールや自動生成ツールに頼るようになり、CREATE TABLE を手で書いたりすることがなくなったからだ。要するに、GUI のツールで開発用サーバ上の DB を直接操作しながら作って、完成した時点でテストサーバや本番サーバに配備するというスタイルで DB まわりを作るようになったので、逆の作業が必要になったということだ。
もちろん、私も初めて DB を作ったときは CREATE TABLE をテキストエディタで手書きしたものだ。その後 CREATE TABLE を自動生成するスクリプトを作ったり、したが、現在は GUI のツールを利用するというスタイルに落ち着いている。私が扱うのはテーブル数15から30程度の小規模な DB なので、それで十分用が済んでしまう。
わざわざ SQL に落とさずに、SQL Server の DTS (Data Transformation Service - データ変換サービス) で転送すれば用が済んでしまう場合もある。ただ、プレーンテキストのスクリプトにするということは、スナップショットとして保存したり、Subversion や PVCS、VSS などの汎用のバージョン管理ツールで履歴を管理できるということだ。これは大きな利点だ。SQL Server は VisualStudio + VSS と緊密に連携することもできるが、サーバの構成などによってはその方法を採れないことも多いため、スクリプト化は非常にありがたい機能だ。
- SQL スクリプト生成の手順
Enterprise Manager の データベースオブジェクトを右クリックし、SQL スクリプト生成を選択。「SQL スクリプト生成ウインドウ」が表示されるので、対象オブジェクトやオプションを指定する。
以下、オプションのメモ。
■はチェックボックスのチェックオンを、□はオフを表す。
●はラジオボタンのチェックオンを、○はオフを表す。
- 「全般」タブ スクリプト化するオブジェクト指定
基本は全オブジェクトを SQL スクリプト化する。抜けがあると怖いので、全オブジェクトのスクリプト(O) を明示的に指定するのがよい。■全オブジェクトのスクリプト(O)
■全テーブル(T)
■全ビュー(W)
■全ストアド プロシージャ(P)
■全デフォルト(D)
■全ルール(U)
■全ユーザー定義データ型(Y)
■全ユーザー定義関数(F)
- 「書式」タブ スクリプト書式オプション
「すべての従属オブジェクトに対してスクリプトを生成(E)」は今回必要ないのでオフにした。スクリプト オプションを使用して、オブジェクト スクリプトを作成する方法を指定できます。
■オブジェクトごとに CREATE <object> コマンドを生成(C)
■オブジェクトごとに DROP <object> コマンドを生成(D)
□すべての従属オブジェクトに対してスクリプトを生成(E)
■スクリプト ファイルに説明用ヘッダーを含める(I)
■拡張プロパティを含める(X)
□7.0 互換の機能のみスクリプトを作成(O)
スクリプト ファイルに説明用ヘッダーを含める(I)をオンにしておくと、対象オブジェクトの名前と現在の日付をコメントとして挿入してくれる。以下のような感じ。「/****** オブジェクト : ストアド プロシージャ dbo.P_GetLandscapeCustomers スクリプト日付 : 2006/08/23 12:43:12 ******/」テキストの差分をとる場合に邪魔になる場合はオフにすればいい。
「拡張プロパティを含める」をオンにしておくと、EnterpriseManager のテーブルデザイナで付加したカラム(列)への説明コメントも含めてスクリプトを生成してくれる。
- 「オプション」タブ スクリプト作成オプション
全部チェックオン。ただし、「データベース スクリプト(C)」はデータベースファイル (.mdf) や トランザクションログファイル (.ldf) の絶対パスをハードコードしてしまう。そのため、パスが異なる環境で実行する場合はチェックをオフにするか、スクリプト中のパスを修正して整合性をとる。■データベース スクリプト(C)
■データベース ユーザーとデータベース ロール スクリプト(S)
■SQL Server ログイン (Windows と SQL Server ログイン) スクリプト(L)
■オブジェクト レベル権限スクリプト(M)
テーブル スクリプト作成オプション
■インデックス スクリプト(X)
■フルテキスト インデックス スクリプト(I)
■トリガ スクリプト(R)
■主キー、外部キー、デフォルトと CHECK 制約スクリプト(K)
ファイル オプション
○ファイル フォーマット
○MS-DOS テキスト (OEM)(D)
○Windows テキスト (ANSI)(W)
●インターナショナル テキスト (Unicode)(U)
生成するファイル
●1 ファイルに作成(F)
○オブジェクトごとにファイル作成(B)
* ナチュラルキーよりサロゲートキー
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [SQL] [Postgres] [MS SQL Server]
RDBMS における主キーや参照整合性制約の外部キーは、ナチュラルキーよりもサロゲートキーを使う方がより変更に強くなる。
要するに、顧客コードとは別にシステムで振り出す ID を格納するカラムを作れということ。参照整合性制約を作成するときにも、サロゲートキーの方を使う。
Web+DB Press の Vol.21 に詳しく書いてある。
顧客コードのフォーマットは変わるかもしれないから。変わってしまった場合、DB の再設計が必要だったり、データの整合性が失われたり、過去のデータとの比較ができなくなったりする。
それ以後、顧客コードなどがあってもシステム側で振り出すユニークな ID を格納するカラムを使うようになった。
- ナチュラルキー
顧客コードなどの、ビジネスにおいて自然に発生するキー。自然キーともいう。- サロゲートキー
レコードを一意に特定するためにシステムが振り出すキー。アイデンティファイア (Identifier) ともいう。Postgres だったらシリアル型、MS SQL Server だったら IDENTITY を使うのが簡単。要するに、顧客コードとは別にシステムで振り出す ID を格納するカラムを作れということ。参照整合性制約を作成するときにも、サロゲートキーの方を使う。
- なぜサロゲートキーを使う方が良いのか
Web+DB Press の Vol.21 に詳しく書いてある。
顧客コードのフォーマットは変わるかもしれないから。変わってしまった場合、DB の再設計が必要だったり、データの整合性が失われたり、過去のデータとの比較ができなくなったりする。
- システム側で振り出したキーを使えと教えられたなあ
そういえば DB 設計をし始めた頃、「システム側で必ずキーを振り出せ」と尊敬する先輩から教えられたなあ。そのときはサロゲートキーという言葉ではなく、「システム側で振り出すキー」という表現だったけど。それ以後、顧客コードなどがあってもシステム側で振り出すユニークな ID を格納するカラムを使うようになった。
- 参考
このメモは L'eclat des jours(2005-01-07) http://arton.no-ip.info/diary/20050107.html#p01 に触発されて書いた。* DATETIME型/TIMESTAMP型の展開と WHERE句の範囲指定
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [SQL] [MS SQL Server] [Postgres]
時刻の扱いは データベースエンジンによって異なるので、クエリを作成する前に仕様や挙動を確認しておきたい。
たとえば、 time_column < '2004-08-27' のとき、'2004-08-27' は いつの時点を指すのか。2004-08-27 00:00:00 なのか、 2004-08-27 **:**:** 、つまりランダムなのか。いつもは時分秒まで指定していたので、省略して表記した場合はどうなるか調べておこう。
http://www.microsoft.com/japan/msdn/library/ja/acdata/ac_8_c ...
要するに SQL Server2000 では、00:00:00 になるってことか。
日本PostgreSQLユーザー会: PostgreSQL 7.2.X 付属ドキュメント
http://www.postgresql.jp/document/pg721doc/
7.4 系列の文書も見てみたが、やはり明確な記述は見つけられなかった。
日本PostgreSQLユーザ会: PostgreSQL 7.4.X 付属ドキュメント
http://www.postgresql.jp/document/pg743doc/
まあ、この後やってみればわかるだろう。たぶん SQL Server2000 と同じ結果になると思うけど。
PostgreSQL 7.2.1 用。
結論から言うと、SQL Server2000 と PostgreSQL 7.2 ともに 'YYYY-MM-DD' は 'YYYY-MM-DD 00:00:00:000' に展開される。以下、サンプル。
PostgreSQL 7.2.1 でも SQL Server2000 でも同じ結果になった。以下、Postgres の出力結果。
さっきのテーブルを DROP して CREATE しなおし、8レコード分のテストデータを INSERT する。
INSERT が終わったら SELECT してみる。今度は SQL Server2000 での出力。
予想通り、2004-08-26 は 2004-08-26 00:00:00 に展開された。
イコールを付ければ8/26のレコードもヒット。
Postgres でも同じように動作した。
たとえば、 time_column < '2004-08-27' のとき、'2004-08-27' は いつの時点を指すのか。2004-08-27 00:00:00 なのか、 2004-08-27 **:**:** 、つまりランダムなのか。いつもは時分秒まで指定していたので、省略して表記した場合はどうなるか調べておこう。
- SQL Server2000 の場合
日付時刻データの使用http://www.microsoft.com/japan/msdn/library/ja/acdata/ac_8_c ...
時刻だけまたは日付だけを個別に格納するデータ型はありません。datetime 型または smalldatetime 型の値を設定する場合、時刻だけを指定すると、日付のデフォルト "January 1, 1900" が使用されます。日付だけを指定すると、時刻のデフォルト "12:00AM (深夜)" が使用されます。
要するに SQL Server2000 では、00:00:00 になるってことか。
- PostgreSQL 7.2.1 の場合。
ドキュメントは見つけられなかった。日本PostgreSQLユーザー会: PostgreSQL 7.2.X 付属ドキュメント
http://www.postgresql.jp/document/pg721doc/
7.4 系列の文書も見てみたが、やはり明確な記述は見つけられなかった。
日本PostgreSQLユーザ会: PostgreSQL 7.4.X 付属ドキュメント
http://www.postgresql.jp/document/pg743doc/
まあ、この後やってみればわかるだろう。たぶん SQL Server2000 と同じ結果になると思うけど。
- CREATE TABLE
SQL Server2000 用。CREATE TABLE time_where_test (utime datetime);
PostgreSQL 7.2.1 用。
CREATE TABLE time_where_test (utime timestamp);
- INSERT 時の展開
まずは INSERT のテスト。結論から言うと、SQL Server2000 と PostgreSQL 7.2 ともに 'YYYY-MM-DD' は 'YYYY-MM-DD 00:00:00:000' に展開される。以下、サンプル。
PostgreSQL 7.2.1 でも SQL Server2000 でも同じ結果になった。以下、Postgres の出力結果。
hiroaki=> INSERT INTO time_where_test VALUES ('2004-06-18');
INSERT 93862442 1
hiroaki=> SELECT * FROM time_where_test;
utime
---------------------
2004-06-18 00:00:00
(1 row)
- SELECT 時の展開
今度は SELECT 時のテスト。おそらく INSERT 時と同じように展開されるだろうけど、念のため。さっきのテーブルを DROP して CREATE しなおし、8レコード分のテストデータを INSERT する。
INSERT INTO time_where_test VALUES ('2004-08-09 02:00:00');
INSERT INTO time_where_test VALUES ('2004-08-20 03:00:00');
INSERT INTO time_where_test VALUES ('2004-08-26 00:00:00');
INSERT INTO time_where_test VALUES ('2004-08-26 04:00:00');
INSERT INTO time_where_test VALUES ('2004-08-26 23:59:59');
INSERT INTO time_where_test VALUES ('2004-08-27 00:00:00');
INSERT INTO time_where_test VALUES ('2004-08-27 09:00:00');
INSERT INTO time_where_test VALUES ('2004-08-27 23:59:59');
INSERT が終わったら SELECT してみる。今度は SQL Server2000 での出力。
SELECT * FROM time_where_test WHERE utime < '2004-08-26' ORDER BY utime;
utime
------------------------------------------------------
2004-08-09 02:00:00.000
2004-08-20 03:00:00.000
(2 件処理されました)
予想通り、2004-08-26 は 2004-08-26 00:00:00 に展開された。
イコールを付ければ8/26のレコードもヒット。
SELECT * FROM time_where_test WHERE utime <= '2004-08-26' ORDER BY utime;
utime
------------------------------------------------------
2004-08-09 02:00:00.000
2004-08-20 03:00:00.000
2004-08-26 00:00:00.000
(3 件処理されました)
Postgres でも同じように動作した。
SQL 利用経験が一年以上の初級者から中級者向けの本とのこと。
* SQL の COALESCE
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [SQL] [Postgres] [MS SQL Server]
COALESCE() は、引数のうち NULL でないものを返す。CASE や NULLIF を使っても同じことができるけど、こっちの方が簡潔。可変長の複数の引数を指定できて便利。
http://www.postgresql.jp/document/pg734doc/user/functions-co ...
COALESCE
http://www.microsoft.com/japan/msdn/library/ja/tsqlref/ts_ca ...
COALESCE の発音は「こぅあれす」なんだけど、「す」のところが SCE になっていることが覚えられない原因だと思う。最初は「こぉりーす」と誤読していたこともスペルミスの原因かも。
coalesce goo 英和辞典
http://dictionary.goo.ne.jp/search.php?MT=coalesce&kind= ...
- COALESCE リファレンス
PostgreSQL 7.3.4 ユーザガイド Chapter 6. 関数と演算子 条件式http://www.postgresql.jp/document/pg734doc/user/functions-co ...
6.12.2. COALESCE
COALESCE(value [, ...])
COALESCE 関数は、NULL でない自身の最初の引数を返します。 データを表示の目的で取り出す際、NULL 値の代わりにデフォルト値を使う場合、時として便利なことがあります。 その例です。
SELECT COALESCE(description, short_description, '(none)') ...
COALESCE
http://www.microsoft.com/japan/msdn/library/ja/tsqlref/ts_ca ...
- COALESCE のスペルが覚えられない
ところで、私は COALESCE のスペルがなぜか覚えられないため、この記事を書いた。COLESE とか COALESE とか COLESCE などと書いてしまい、文法エラーになることが多かった。最近では MS SQL Server 付属のクエリアナライザで SQL を書くようになり、キーワード色づけの恩恵を受けるようになったため間違いが減ったけど。それでも時々忘れることがあって、そのときはクエリアナライザの顔色を伺いながらタイプしている。キーワードに色が付いたら正解スペル、色が付かなかったら間違いスペルだ。まさに顔色を伺ってる感じだ。COALESCE の発音は「こぅあれす」なんだけど、「す」のところが SCE になっていることが覚えられない原因だと思う。最初は「こぉりーす」と誤読していたこともスペルミスの原因かも。
coalesce goo 英和辞典
http://dictionary.goo.ne.jp/search.php?MT=coalesce&kind= ...
- SQL ではエスケープしなければならない文字がある。
今回は、MS SQL Server での文法をメモする。Postgres での文法は、以下を参照。
[pgsql-jp: 26535] % で始まる文字の LIKE 検索
http://ml.postgresql.jp/pipermail/pgsql-jp/2002-June/001677. ...
PostgreSQL 7.3.2 ユーザガイド Chapter 6. 関数と演算子
http://search.net-newbie.com/pgsql/functions-matching.html
- ' のエスケープ
クエリ中に ' がある場合、' を付加する。例)
クエリが I'll be back のとき
SELECT * FROM table WHERE string = 'I''ll be back';
- LIKE で使われるメタ文字のエスケープ
クエリ中に以下の文字列がある場合、# を付加する。% (パーセント)
_ (アンダーバー)
[ (角カッコ)
] (角カッコ)
^ (ハット)
# (エスケープ文字自体をエスケープするには、同じ文字を続ける)
# を選んだ意味は特にない。他の文字でエスケープしたければ、 ESCAPE 句で指定できる。
ESCAPE '#'
例)
クエリが 100% のとき。
LIKE '%100#%%' ESCAPE '#'
クエリが 100# のとき。
LIKE '%100##%' ESCAPE '#'
クエリが K's dining #100 のとき
LIKE '%K''s dining ##100%' ESCAPE '#'
- テストに使った SQL
CREATE TABLE esc (string VARCHAR(100));INSERT INTO esc (string) VALUES ('100%');
INSERT INTO esc (string) VALUES ('it is ');
INSERT INTO esc (string) VALUES ('%');
INSERT INTO esc (string) VALUES ('%%%');
INSERT INTO esc (string) VALUES ('[');
INSERT INTO esc (string) VALUES (']');
INSERT INTO esc (string) VALUES ('^');
INSERT INTO esc (string) VALUES ('^ff');
INSERT INTO esc (string) VALUES ('10_');
INSERT INTO esc (string) VALUES ('_');
INSERT INTO esc (string) VALUES ('others');
INSERT INTO esc (string) VALUES ('表');
INSERT INTO esc (string) VALUES ('#1');
SELECT string FROM esc;
SELECT string FROM esc WHERE string LIKE '%#%';
SELECT string FROM esc WHERE string LIKE '%##%' ESCAPE '#';
* UPSERT / MERGE するトリガ MS SQL Server 2000版
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [SQL] [MS SQL Server]
2003-06-03 のトリガは、もっと簡潔に書けることに気づいた。環境は MS SQL Server 2000。
- 冗長版
GO
DROP TRIGGER log_transaction_ao;
GO
CREATE TRIGGER log_transaction_ao ON users FOR INSERT, UPDATE AS
BEGIN
INSERT INTO users_log SELECT * FROM inserted
IF (SELECT COUNT(*) FROM inserted WHERE commit_master = 1) > 0 BEGIN
DECLARE @user_cd VARCHAR(5)
SELECT @user_cd = user_cd FROM deleted
IF (@user_cd IS NOT NULL) BEGIN
DELETE FROM user_master WHERE user_cd = @user_cd
END
INSERT INTO user_master SELECT * FROM inserted
END
END
- 簡潔版
GO
DROP TRIGGER log_transaction_users;
GO
CREATE TRIGGER log_transaction_users ON ao FOR INSERT, UPDATE AS
BEGIN
INSERT INTO user_log SELECT * FROM inserted
IF (SELECT COUNT(*) FROM inserted WHERE commit_master = 1) > 0 BEGIN
DELETE FROM user_master WHERE user_cd IN (SELECT user_cd FROM deleted)
INSERT INTO user_master SELECT * FROM inserted
END
END
* トリガによるデータのハンドリング
この記事の直リンクURL: Permlink | この記事が属するカテゴリ: [SQL] [MS SQL Server]
- トリガを使うことで、INSERT/UPDATE 文を複数のテーブルにそのまま反映させることができる。
- 「ユーザが入力した内容の履歴を取りたい」というリクエストを受けた。本番のテーブルへ投げられたクエリと同じクエリを別途用意したログ用のテーブルに追記していく、というやり方で実現できるだろう。
また、マネージャからは、「プログラム側で複数のテーブルに SQL を投げるのはやらせたくない。欠落したりするおそれがある。何か仕組みを作れないか考えてみてくれ。」とも言われている。- 解決策
ワーク用テーブルとマスターテーブルとログ用テーブルを用意する。ログ用テーブルのテーブル定義は、マスターテーブルと基本的に同じだが、
ユニーク制約、参照整合性制約などは指定しないようにする。
本番用テーブルに以下のような INSERT/UPDATE トリガをセットする。
OLD 特殊テーブルを参照し、その内容をログ用テーブルに INSERT する。
MS SQL Server では、OLD テーブルは inserted と呼ばれている。
postgres などとは名前が違うだけで、機能は変わらないようだ。
ただ、Postgres はトリガの発動タイミングを指定できるが、
MS SQL Server 2000 では指定できない。トリガが発動するのは、制約チェックなどの後だけだ。
・・・と思ったら、MS SQL Server 2000 にも事前に発動するトリガがあるそうな。
INSTEAD OF トリガというもので、従来の事後発動トリガは AFTER トリガと呼ぶとのこと。
- サンプル
-- log テーブルへは無条件に INSERT する。-- master テーブルへは、commit_master カラムが1の時のみ DELETE & INSERT する。
INSERT INTO user_master SELECT * FROM inserted では、inserted が複数レコードでもすべて INSERT してくれる。
DROP TRIGGER log_transaction_user;
CREATE TRIGGER log_transaction_user ON user FOR INSERT, UPDATE AS
BEGIN
INSERT INTO user_log SELECT * FROM inserted
IF (SELECT COUNT(*) FROM inserted WHERE commit_master = 1) > 0 BEGIN
DECLARE @user_cd VARCHAR(5)
SELECT @user_cd = user_cd FROM inserted
DELETE FROM user_master WHERE user_cd = @user_cd
INSERT INTO user_master SELECT * FROM inserted
END
END
使用済みフラグが false の行を SELECT し、データを取得。
その後、使用済みフラグを立てるという処理を考える。
環境は PostgreSQL 7.2.1 を想定。
この問題は、SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
で単純にトランザクションレベルを上げても対処できない。
1: BEGIN;
2: SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
3: SELECT id, data FROM table WHERE flag = FALSE;
4: ------ アプリケーションの処理 ------
5: UPDATE table SET flag = FALSE;
6: COMMIT;
これだと、3 の SELECT が終わった後に別のトランザクションが開始した場合、
更新予定の行も SELECT で読み込まれてしまう。
結果、同じ行を SELECT してしまい、その後 5 のUPDATE を発行しようとした時点で
SERIALIZABLE の分離チェックに引っかかり、2つ目のトランザクションはアボートしてしまう。
ERROR: Can't serialize access due to concurrent update
エラーを発生させずに処理を行うためには、更新トランザクションだけではなく
参照トランザクションもブロックしなければならない。
つまり、この問題はトランザクションレベルの設定では解決できない。
ACCESS EXCUSIVE MODE でロックすることで、SELECT 文自体もブロックされる。
1: BEGIN;
2: LOCK TABLE table IN ACCESS EXCLUSIVE MODE;
3: SELECT id, data FROM table WHERE flag = FALSE;
4: ------ アプリケーションの処理 ------
5: UPDATE table SET flag = FALSE;
6: COMMIT;
LOCK TABLE uniq_uri IN ACCESS EXCUSIVE MODE;
SELECT TOP 1 uri FROM uniq_uri WHERE digit = 5 AND last_update IS NULL;
COMMIT TRANSACTION;
http://www.postgresql.jp/document/pg732doc/user/mvcc.html
[pgsql-jp: 29112] 同時更新の制御2
http://ml.postgresql.jp/pipermail/pgsql-jp/2003-February/004 ...
その後、使用済みフラグを立てるという処理を考える。
環境は PostgreSQL 7.2.1 を想定。
この問題は、SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
で単純にトランザクションレベルを上げても対処できない。
1: BEGIN;
2: SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
3: SELECT id, data FROM table WHERE flag = FALSE;
4: ------ アプリケーションの処理 ------
5: UPDATE table SET flag = FALSE;
6: COMMIT;
これだと、3 の SELECT が終わった後に別のトランザクションが開始した場合、
更新予定の行も SELECT で読み込まれてしまう。
結果、同じ行を SELECT してしまい、その後 5 のUPDATE を発行しようとした時点で
SERIALIZABLE の分離チェックに引っかかり、2つ目のトランザクションはアボートしてしまう。
ERROR: Can't serialize access due to concurrent update
エラーを発生させずに処理を行うためには、更新トランザクションだけではなく
参照トランザクションもブロックしなければならない。
つまり、この問題はトランザクションレベルの設定では解決できない。
- 解決するには、LOCK 文で行またはテーブルをロックする必要がある。
LOCK TABLE table IN ACCESS EXCLUSIVE MODE;ACCESS EXCUSIVE MODE でロックすることで、SELECT 文自体もブロックされる。
1: BEGIN;
2: LOCK TABLE table IN ACCESS EXCLUSIVE MODE;
3: SELECT id, data FROM table WHERE flag = FALSE;
4: ------ アプリケーションの処理 ------
5: UPDATE table SET flag = FALSE;
6: COMMIT;
- サンプル
BEGIN TRANSACTION;LOCK TABLE uniq_uri IN ACCESS EXCUSIVE MODE;
SELECT TOP 1 uri FROM uniq_uri WHERE digit = 5 AND last_update IS NULL;
COMMIT TRANSACTION;
- 参考:
PostgreSQL 7.3.2 ユーザガイド Chapter 9. 同時実行制御http://www.postgresql.jp/document/pg732doc/user/mvcc.html
[pgsql-jp: 29112] 同時更新の制御2
http://ml.postgresql.jp/pipermail/pgsql-jp/2003-February/004 ...
n 日前の日付を求める SQL。
例) 2002年12月3日の7日前を求める。
00:00:00 になるのか、SELECT した時刻になるのかは、実装依存?
それとも、SQL の規格で決まってるのかな?
例) 2002年12月3日の7日前を求める。
# SELECT to_date(now() + '-7day', 'YYYY-MM-DD');時刻の部分はどうなるかは、試してみる必要あり。
to_date
------------
2002-11-26
(1 row)
00:00:00 になるのか、SELECT した時刻になるのかは、実装依存?
それとも、SQL の規格で決まってるのかな?
- OUTER JOIN するとき、NATURAL を指定すれば、ON 節でカラム名を明示しなくて済む、という事でやってみたのだが、非常に遅い。
カラム名で指定: Total runtime: 1727.18 msec
NATURAL で指定: Total runtime: 3849.53 msec
倍近く遅くなってる。NATURAL だと、どのカラムで JOIN できるかどうかを探索するため遅くなっているのだろうか。以下に EXPLAIN ANALYZE の結果を載せておく。
- カラム名で指定したときの SQL : Total runtime: 1727.18 msec
Unique (cost=31062.45..31092.26 rows=92 width=260) (actual time=1675.56..1723.79 rows=150 loops=1)
-> Sort (cost=31062.45..31062.45 rows=918 width=260) (actual time=1675.42..1675.88 rows=156 loops=1)
-> Merge Join (cost=30950.59..31017.30 rows=918 width=260) (actual time=442.96..474.64 rows=156 loops=1)
-> Index Scan using summary_pkey on summary (cost=0.00..52.00 rows=1000 width=80) (actual time=0.27..0.27 rows=0 loops=1)
-> Sort (cost=30950.59..30950.59 rows=918 width=180) (actual time=442.35..443.03 rows=156 loops=1)
-> Hash Join (cost=4.90..30905.44 rows=918 width=180) (actual time=15.61..439.20 rows=156 loops=1)
-> Merge Join (cost=0.00..30884.49 rows=918 width=123) (actual time=8.90..423.29 rows=156 loops=1)
-> Index Scan using sysqa_pkey on account (cost=0.00..30826.80 rows=578 width=115) (actual time=7.38..339.52 rows=32 loops=1)
SubPlan
-> Materialize (cost=26.48..26.48 rows=8 width=4) (actual time=0.01..0.09 rows=32 loops=1157)
-> Index Scan using doc_property_system_code on doc_property (cost=0.00..26.48 rows=8 width=4) (actual time=1.00..3.66 rows=32 loops=1)
-> Index Scan using doc_property_serialnum on doc_property (cost=0.00..40.19 rows=1835 width=8) (actual time=0.93..61.53 rows=1703 loops=1)
-> Hash (cost=4.52..4.52 rows=152 width=57) (actual time=6.33..6.33 rows=0 loops=1)
-> Seq Scan on system_name (cost=0.00..4.52 rows=152 width=57) (actual time=0.33..4.38 rows=152 loops=1)
- NATURAL で指定したときの SQL: Total runtime: 3849.53 msec
Unique (cost=31070.42..31100.24 rows=92 width=292) (actual time=3800.17..3846.17 rows=150 loops=1)
-> Sort (cost=31070.42..31070.42 rows=918 width=292) (actual time=3800.02..3800.45 rows=156 loops=1)
-> Merge Join (cost=31020.42..31025.27 rows=918 width=292) (actual time=2382.34..2412.63 rows=156 loops=1)
-> Sort (cost=30950.59..30950.59 rows=918 width=180) (actual time=2381.23..2381.78 rows=156 loops=1)
-> Hash Join (cost=4.90..30905.44 rows=918 width=180) (actual time=15.27..438.47 rows=156 loops=1)
-> Merge Join (cost=0.00..30884.49 rows=918 width=123) (actual time=8.46..422.62 rows=156 loops=1)
-> Index Scan using sysqa_pkey on account (cost=0.00..30826.80 rows=578 width=115) (actual time=7.18..339.89 rows=32 loops=1)
SubPlan
-> Materialize (cost=26.48..26.48 rows=8 width=4) (actual time=0.01..0.07 rows=32 loops=1157)
-> Index Scan using doc_property_system_code on doc_property (cost=0.00..26.48 rows=8 width=4) (actual time=0.81..3.46 rows=32 loops=1)
-> Index Scan using doc_property_serialnum on doc_property (cost=0.00..40.19 rows=1835 width=8) (actual time=0.72..60.75 rows=1703 loops=1)
-> Hash (cost=4.52..4.52 rows=152 width=57) (actual time=6.39..6.39 rows=0 loops=1)
-> Seq Scan on system_name (cost=0.00..4.52 rows=152 width=57) (actual time=0.32..4.41 rows=152 loops=1)
-> Sort (cost=69.83..69.83 rows=1000 width=112) (actual time=0.74..0.74 rows=0 loops=1)
-> Seq Scan on summary (cost=0.00..20.00 rows=1000 width=112) (actual time=0.02..0.02 rows=0 loops=1)