- PR -

PostgreSQL での同時挿入について

投稿者投稿内容
がるがる
ぬし
会議室デビュー日: 2002/04/12
投稿数: 873
投稿日時: 2003-11-28 15:05
がると申します。
…なんていうか、たわごとなので軽く流し読みしていただければ :-P

どうも、DBのシリアル機能が(DBMS毎に差異があるという点を含めて)苦手で。
そういう機能を「まったく使わずに」実装することがよくあります。
で、そういった実装のうち「もっともシンプル(単純)な方法」を、軽く。

取り合えず、重要なのは「受注ID」だと思います。ここさえユニークに取得できれば、あとはまぁどうとでもなりそうなわけで。
Lengthが特に問題ない場合、私は以下のようなIDをよく用いてます。

現在時刻(通算秒)-現在時刻(マイクロ秒)-プロセスID

スレッドが極端に多い場合に一応ひっかかりますが、その辺はINSERTでチェックすれば、プライマリKeyで引っかかるので検出が可能です。
そんなに激しくないシステムの場合、マイクロ秒を省いたり、プロセスIDを省いたりしても問題なく動きます。

で、今回の場合、私ならトランザクションは使わないです。
なんでかってぇと、周囲と重複して困るのは発注IDのみで、それならトランザクションで切らなくても、INSERTできればOK、出来なければNGで容易に判定が出来るからです。

・まずは受注テーブルにINSERTをして
・問題が無ければ受注明細テーブルにのんびりINSERTして

で、楽に流せると思います。

一応、この方法ですと「あとでDBMSを変えるとき」に困らないです。
なんていうか「生活の知恵」っていうか「手抜きの知恵」っていうか(^^;

興味があったらお試しください。
& もし「なんでトランザクションがいらないの?」という疑問があるときは、危険そうなパターン込みで質問していただければ、きちんと説明いたします(^^
knock
会議室デビュー日: 2003/09/02
投稿数: 17
投稿日時: 2003-11-28 15:06
皆さんありがとうございます。
非常にわかりやすく多くのことを理解できました。

アティさん>
currval 含めてシーケンスについての学習をこれからやっていこうと思います。
プリンスさん>
テーブルロックはよく状況を考えて使いましょうという感じですね。
# まだまだそれほど大きなシステムを構築できる経験がないですが...
ただし今回でロックやトランザクションについての基礎知識がついたと思います。

皆さんに教えていただいた色々な方法で試して、実際の挙動を見てみたいと思います。
ありがとうございました。

今後ともよろしくお願いします。
knock
会議室デビュー日: 2003/09/02
投稿数: 17
投稿日時: 2003-11-28 15:14
引用:

がるがるさんの書き込み (2003-11-28 15:05) より:

現在時刻(通算秒)-現在時刻(マイクロ秒)-プロセスID

スレッドが極端に多い場合に一応ひっかかりますが、その辺はINSERTでチェックすれば、プライマリKeyで引っかかるので検出が可能です。
そんなに激しくないシステムの場合、マイクロ秒を省いたり、プロセスIDを省いたりしても問題なく動きます。

で、今回の場合、私ならトランザクションは使わないです。
なんでかってぇと、周囲と重複して困るのは発注IDのみで、それならトランザクションで切らなくても、INSERTできればOK、出来なければNGで容易に判定が出来るからです。

・まずは受注テーブルにINSERTをして
・問題が無ければ受注明細テーブルにのんびりINSERTして

で、楽に流せると思います。



がるがるさん、丁度入れ違いになってしまったようですね...
ご返答ありがとうございます。

なるほど、トランザクションにこだわらずに受注ID レベルでうまくユニークな値を
作るという感じでしょうか。
ちょっと気になったのですがこの場合には、受注テーブルにINSERTをした受注ID と
受注明細テーブルにINSERTする受注ID との関連付けはどのように行うのが
よろしいのでしょうか。

なんとなくトランザクションを使わないと処理の単位(?)がなくなってしまって
個人的には怖い印象を持ちました。
またこのような方法はよく用いられるのでしょうか?
プリンス
ベテラン
会議室デビュー日: 2003/07/05
投稿数: 78
お住まい・勤務地: 神奈川
投稿日時: 2003-11-28 15:41
引用:

がるがるさんの書き込み (2003-11-28 15:05) より:
がると申します。
…なんていうか、たわごとなので軽く流し読みしていただければ

どうも、DBのシリアル機能が(DBMS毎に差異があるという点を含めて)苦手で。
そういう機能を「まったく使わずに」実装することがよくあります。
で、そういった実装のうち「もっともシンプル(単純)な方法」を、軽く。


うーん?トランザクションとはシリアル機能を実現するわけではなく、
ACIDを保障するものだと思ったんですが?
(もちろんオラクルの場合、トランザクションのアイソレーションレベルをシリアライザブルにすることで楽観的ロックが出来ますけど)
つまり、ここで重要なのは受注ヘッダと受注明細に対するINSERT文をアトミックにすることにあります。たとえば、受注ヘッダにINSERTしたあとに、データベースが落っこちたら、受注ヘッダにはレコードはあるけど、受注明細には存在しないことになり、
ACIDを破ることになります。よく引き合いに出されるのは、銀行等の口座振替で普通口座から当座にお金を移動するときや、口座振込みの出金、入金などです。トランザクションなくしてACIDを実現することは出来ないんでは?といいますか、トランザクションとはACIDを実現することのように思えます。
口調が強く感じましたらごめんなさい。余談ですけど、今アットマークITのトップにある、「スーパーコンサルタントはここが違う!」は分かってはいるけどなかなか実践できませんね。けど、一人一人が注意しなければと思いました。
がるがる
ぬし
会議室デビュー日: 2002/04/12
投稿数: 873
投稿日時: 2003-11-28 16:19
引用:

knockさんの書き込み (2003-11-28 15:14) より:
なるほど、トランザクションにこだわらずに受注ID レベルでうまくユニークな値を
作るという感じでしょうか。
ちょっと気になったのですがこの場合には、受注テーブルにINSERTをした受注ID と
受注明細テーブルにINSERTする受注ID との関連付けはどのように行うのが
よろしいのでしょうか。

なんとなくトランザクションを使わないと処理の単位(?)がなくなってしまって
個人的には怖い印象を持ちました。
またこのような方法はよく用いられるのでしょうか?


む、記述が足りなかった。申し訳ないです。
受注テーブルと受注詳細テーブルの受注IDは「同じもの」でよいと思います。
で、受注詳細IDは「同一受注IDに対して」ユニークであればよいので、単純に連番で1、2、…とつけていけばよいと思うです。

で、よく用いられる方法、かという質問には…おいらの周りではYes、というくらいです。
ある程度色々な会社を渡り歩いているのでそんなに狭い見識ではないと思うのですが、とはいえ個人で渡り歩く量には限りがあるので(^^;
ただ、ユニークなIDというのはかなりよく出てくる話なので、時間軸、ないし時間軸+プロセスIDというのは、非常にお手軽なので便利です。

トランザクションの話は、プリンスさんへのレスでまとめてお話をします(^^
がるがる
ぬし
会議室デビュー日: 2002/04/12
投稿数: 873
投稿日時: 2003-11-28 16:31
引用:

プリンスさんの書き込み (2003-11-28 15:41) より:
うーん?トランザクションとはシリアル機能を実現するわけではなく、
ACIDを保障するものだと思ったんですが?
(もちろんオラクルの場合、トランザクションのアイソレーションレベルをシリアライザブルにすることで楽観的ロックが出来ますけど)
つまり、ここで重要なのは受注ヘッダと受注明細に対するINSERT文をアトミックにすることにあります。たとえば、受注ヘッダにINSERTしたあとに、データベースが落っこちたら、受注ヘッダにはレコードはあるけど、受注明細には存在しないことになり、
ACIDを破ることになります。よく引き合いに出されるのは、銀行等の口座振替で普通口座から当座にお金を移動するときや、口座振込みの出金、入金などです。トランザクションなくしてACIDを実現することは出来ないんでは?といいますか、トランザクションとはACIDを実現することのように思えます。
口調が強く感じましたらごめんなさい。


んと。口調はお気になさらずに(^^

で、本題を。
銀行口座の場合、出金と入金が「独立した状態で」存在しうるデータ操作で、かつ、振込みの場合は「連続していなければならない」動作であるために、トランザクションは非常に重要で、かつ必ず用いられていると思います(ないところがあったら嫌だなぁ :-P)
独立というのは、簡単に言うと「入金だけ」「出金だけ」というデータ操作が理論的にありえる(引き出しとかATMからの入金とか)ことを指します。

今回の場合、受注テーブルと受注詳細テーブルは「独立した状態で」は存在しないデータ操作になります(場所によっては「受注テーブルはあるけど注文が0であるために詳細は0テーブル」というケースがありえますが、比較的レアだと思われるので無視します)。

無論、DBの整合性が取れているほうが好ましいのは事実なのですが。
一つには
・整合性の取れていないDBは簡単に抽出できる
という部分があるためにMust(必須)ではなく、逆に
・トランザクションによってDBへの負荷が余分にかかる
ことが、場合によっては問題視される場合すらあります。

例題の銀行の場合は「起きちゃまずいうえにDBだけからでは整合性のとりようが無い」のが致命的ですが。
今回の場合は、受注テーブルに「受注詳細数(受注詳細に書き込むレコードの数)」を入れる程度の細工で容易に問題が検出できるので。

つまり、私が気にしているポイントは
・ACIDが破られるか否か
よりは
・ACIDが破られた時に容易に発見できるか?
ということになるかなぁ、と思います。
まぁ、詳細テーブルへのINSERTの途中で落ちてしまえば、中途半端にしろ全部にしろデータがすっ飛ぶことに変わりは無いので(こういうときのために、Logはとっても重要です)。
データのチェックをして「このデータはおかしい」ということさえわかれば、中途半端にデータが入っていてもそんなに問題ないケースが多いと私は思っています。

集計とかを考えるなら、cronあたりで1日一回くらい、データを掃除すればよいし。そうすると「危ないデータ」が識別できるからアラートもだせるので。

上記のような理由から、トランザクションを使わなくてもできる、という風な話を持ち出しています。
多分、私のテリトリー上の問題も大きいと思うのですが。トランザクションは、便利である分色々な意味で「コストの高い」処理なので、使わずに回避できる限りは可能な限り回避したい、というスタンスを私はとっています。

「なんか変〜」という反応は大歓迎ですので、じゃかすか突っ込んでください(笑
プリンス
ベテラン
会議室デビュー日: 2003/07/05
投稿数: 78
お住まい・勤務地: 神奈川
投稿日時: 2003-11-28 18:04
引用:

がるがるさんの書き込み (2003-11-28 16:31) より:

つまり、私が気にしているポイントは
・ACIDが破られるか否か
よりは
・ACIDが破られた時に容易に発見できるか?
ということになるかなぁ、と思います。
まぁ、詳細テーブルへのINSERTの途中で落ちてしまえば、中途半端にしろ全部にしろデータがすっ飛ぶことに変わりは無いので(こういうときのために、Logはとっても重要です)。
データのチェックをして「このデータはおかしい」ということさえわかれば、中途半端にデータが入っていてもそんなに問題ないケースが多いと私は思っています。

集計とかを考えるなら、cronあたりで1日一回くらい、データを掃除すればよいし。そうすると「危ないデータ」が識別できるからアラートもだせるので。

上記のような理由から、トランザクションを使わなくてもできる、という風な話を持ち出しています。
多分、私のテリトリー上の問題も大きいと思うのですが。トランザクションは、便利である分色々な意味で「コストの高い」処理なので、使わずに回避できる限りは可能な限り回避したい、というスタンスを私はとっています。

「なんか変〜」という反応は大歓迎ですので、じゃかすか突っ込んでください(笑



がるがるさま、非常に勉強になりました。なるほど受注ヘッダと受注明細のような1:Nのコンポジットの場合、結構うまくいくもんですね。
私がこれまで作ってきたシステムは小規模なもので、トランザクションも1分に1回あればいいくらいの負荷のないものだったので、安易にトランザクションを使ってました。
トランザクションが多い世界では逆にトランザクションを使わないことでスループットを上げる方法があるんですね。
永井和彦
ぬし
会議室デビュー日: 2002/07/03
投稿数: 276
お住まい・勤務地: 東京都
投稿日時: 2003-12-01 10:32
引用:

アティさんの書き込み (2003-11-28 14:10) より:

knockさんの書いた場合も、安全だと思いますよ。
curvalで取得される値は、自セッションで取得された最新の値ですから。



SEQUENCEの更新はTransactionと関係無く行われる(=RollBackしてもSEQUENCEの値は
戻らない)というのが頭に残っていまして、その部分で私が混乱していたようです。

確かに、この局面で使えないのであれば、Curvalの意味が無いですよね。

Re: INSERT されたレコードのシルアル値の取得

辺りに書いてありました。元がそうだったので、とりあえずPostgreSQLで話を進めますが

PostgreSQL 7.3.2 ユーザガイド Prev Chapter 6. 関数と演算子

辺りも一緒に目を通しておくといいかも知れません。

1. nextval(受注ID)を使って受注テーブルにINSERT
2. curvalを使って受注詳細テーブルにINSERT
3. COMMIT

で、よかったんですね。

自分が曖昧に理解してたところがすっきり整理されて助かりました。
ありがとうございました。>knockさん、アティさん

スキルアップ/キャリアアップ(JOB@IT)