トップページ⇒tech
62コメント28KB

ASP+SQL Serverで切実に困ってます。

■ このスレッドは過去ログ倉庫に格納されています
0001デフォルトの名無しさんNGNG
自分で本やWebを調べたんですけどわかんなくて・・・
たぶんすっごい基本的なことだと思うんで知ってる人いたらタスケテ。

ASP+MS SQL Serverで作ったWebのサービスに異常発生。
調べてみたらユニークでなきゃいけないIDがかぶってました。
どうやら
1.一番大きいIDを引っ張ってくる。
↓
2.それに+1する
↓
3.+1したIDをつけてINSERT。

っていうところで1→3が終わる前に他のユーザが1の処理を
はじめるとIDがかぶるってことがわかりました。
(ためしに自分で2台同時にボタンおしたらかぶりました)

会社でほかにわかる人がいないので凄い基本っぽい気がする
この問題が解決できません(涙
ロックとかトランザクションって調べたんですけどいまいち意味がわかってない
せいかうまくいきません。
17日の正午までメンテナンス中っていうページに差し替えて
しのいであるんだけどこのままじゃ・・・。
0002デフォルトの名無しさんNGNG
ADOを使っているとして、

処理1の前に
conn.BeginTrans
して、
処理1-3が成功したら
conn.CommitTrans
どこかで失敗したら
conn.RollbackTrans
する。(connはConnectionオブジェクト)

でもちゃんと勉強した方がいいよ。トランザクションも知らずにDB
アプリを組むのは無謀ですよ。
0003デフォルトの名無しさんNGNG
こたえてくれてありがとうございます。

上のような感じにやってるんですが、その「失敗したか」
っていうのをCommitTransする前にどうやって判断したらいいかがわからなくて・・・。
00041NGNG
CON.BeginTrans()

SQL = "SELECT TOP 1 bk_id FROM book ORDER BY bk_id DESC"
SET RS = CON.Execute(SQL)

If RS.EOF Then
newbk_id = 1
Else
newbk_id = RS("bk_id")+1
End If

SET rsInsert = Server.CreateObject("ADODB.Recordset")
rsInsert.Open "book", CON, 3, 3

rsInsert.AddNew
rsInsert("bk_id") = newbk_id
rsInsert.Update
rsInsert.Close

SQL = "SELECT TOP 1 bk_id FROM book ORDER BY bk_id DESC"
SET RS2 = CON.Execute(SQL)

if RS2("bk_id")+1 = newbk_id then
CON.CommitTrans
else
CON.RollBackTrans
response.redirect "error.asp"
end if

こんな感じなんですがものすごくだめな気がします>オレ
だれかお助けを・・・もちろん土日も会社です(今も)
うっ
0005デフォルトの名無しさんNGNG
SQLServer初心者だけど検索してみた。
http://www.acc.gr.jp/wwwdata/pinsql/19981228/836.html

本当は、設計が悪いといいたけどそういわれてもこまるでしょう。
bookをテーブルロックしたら副作用が出るかもしれないけど
その辺は全体をみないとわからないからとりあえず。
0006デフォルトの名無しさんNGNG
レコード作成してる糞処理をストアドにして
Application.Lock
ストアド実行
Application.Unlock
でダメ?俺もASP知らないけど、カウンタのサンプルでApplication.Lockって使ってたよ
0007<>NGNG
> 会社でほかにわかる人がいないので凄い基本っぽい気がする
> この問題が解決できません(涙
どんな会社だよ。本当か?
教えてもいいけどさ、つか他の人がロジックまで丁寧に書いてくれてる
からそれ以上言わないけどさ、
根本的に、誰も排他制御も知らん会社で仕事してる事が一番の問題
な気がするぞ。転職すれ。

君のコードで言うと
> SET RS2 = CON.Execute(SQL)
これをやったときに、RS2のプロパティかメソッドで何らかエラーが
拾えるはずだが(調べるのメンドイからそこは割愛するが、ワリィ)
その結果をによってCommitTransもしくはRollback
って感じじゃないのかね?
0008デフォルトの名無しさんNGNG
SQLServerならadLockOptimistic(3)でなくadLockPessimistic(2)でやってみれば?
他がオープン中のときは、処理が終わるまで待たされるようになる。
sSQL = "SELECT TOP 1 bk_id FROM book ORDER BY bk_id DESC"
Call rs.Open("book", CON, 3, 2)
If rs.EOF = True Then
  lNewId = 1
Else
  lNewId = rs("bk_id") + 1
End If
rs.AddNew
rs("bk_id") = lNewId
rs.Update
rs.Close

SQLServerなど信用できんというならば、ファイルロックを使うかな。
排他でオープンしようとするとエラーになるのでOnErrorResumeNextでErr.Number
をチェックして、オープンできるまでぐるぐる繰り返す。

OSのファイル処理など信用できんというならば、、、知らん。
0009 NGNG
1.一番大きいIDを引っ張ってくる。
SELECT MAX(bk_id) FROM book WITH(ROWLOCK)
0010 NGNG
DBの人はセマフォ知らないのか?
00111NGNG
Applicationってやつで他の奴が処理中だったら待つように作ってみたんだけど
「10人同時に押したら10人分まつのか!」っていわれてやり直しに・・・。
結局IDがかぶっててもいいように作り変えることになりました。
なんか負けた感が強いです。
0012デフォルトの名無しさんNGNG
「DBの人」って?
Dドラゴン
Bボール
ですか?クリリンとか
0013デフォルトの名無しさんNGNG
>>1
上司も上司に言い切られるあなたも逝って良し。
>>10
DBの人というかVBの人かな。俺も良く分からん。
ミューテックスとかセマフォとかVBSから使えるのだろうか。
Application.Lockてのがそれっぽいですが。
MSDNより引用
> <%
>   Application.Lock
>   Application("Counter") = Application("Counter") + 1
>   Application.UnLock
> %>
> Lock すると、他のユーザーからの操作をロックし、
> そのアクセスしたユーザーだけが値を変更でき、
> UnLock すると、ロック状態を解除できるというわけです。
> こうすることで、ユーザによる値の変更を競合なく
> おこなっていくことができるようになるわけですね
0014 NGNG
SQLサーバ側のオートナンバーじゃだめなのかな。
アクセスであるやつ。
0015デフォルトの名無しさんNGNG
>>14
IDENTITYですな。

>>10
セマフォ? なぜここでセマフォが?
単なるトランザクション処理でしょ。
00161NGNG
>8
その方法、ズシリときた!とてもいい感じがします。
次からそれでいこう。

>9
ROWLOCKってのはいつからいつまでLOCKするんですか?
もしかして
SELECT MAX(bk_id)+1 As new_id FROM book WITH(ROWLOCK)
とかして次にすぐInsertすれば解決なのかな?

>13
たしかに・・・。
ASPはじめてへぼいBBSが作れたと思った次の仕事がこれで
ようするにおれじゃまだむり・・・。
上司(というか社長)も自分はできるっていってるけど
ロックもトランザクションもやってなかったから知らないと思う。
(それを言うと知ってるって言う)

Webのデザインの会社なんで他に知ってる人いない・・・
おれがちょっと掲示板ぐらいだったら・・・って言ったら
全部やらされてて辛いっス。

>14
なんかオートナンバーは遅くなるからいかん
っていわれた。
おれはよくわかんない。


なんかIDがダブってもいいように作れって言われてるんですけど
なんかその場しのぎな感じがして嫌です。
UniqなIDを確実に作る処理って絶対必要な気がして。
00178=13NGNG
訂正
×Call rs.Open("book", CON, 3, 2)
○Call rs.Open(sSQL, CON, 3, 2)

>>1
IDがダブったらシステム的に話になりません。
オートナンバーは書き込んでみないと何番になったか分からんから、
その値で他のテーブルも更新しようとすると、もう一度読み直さなきゃきゃならんので、
俺的には自分で発番するほうが好き。発番用の管理テーブルを作ったりする場合も有り。

素人でも作れてしまうのが、VBの良いところでもあり、悪いところでもあるかな。
将来、VBが分かるようになってきたからといって、勘違いして複雑なシステムを請け負わないように。
00189NGNG
BEGIN TRAN
SELECT MAX(bk_id) As new_id FROM book WITH(UPDLOCK)
...
INSERT INTO book SELECT ... でMAX(bk_id)+1を追加
COMMIT

*COMMITするまでBOOKはSELECT文でさえLOCKされます。
0019デフォルトの名無しさんNGNG
>>1
SQL Server だと、常套手段はこれ。

1 主キー値に IDENTITY 属性をつけて、SQL サーバ側で採番させる。
2 INSERT 直後に @@IDENTITY で番号を取得。

>>16
IDENTITY は、それほど遅くないですよ。ためしに IDENTITY 有り・無しで
テーブルを作って INSERT の速度を測ってみると良いです。
0020デフォルトの名無しさんNGNG
SET LOCK_TIMEOUT ミリ病
00218=13NGNG
うう、@@IDENTITY知らんかった。
SELECT @@IDENTITY AS 'Identity'
で取れるのか。
素朴な疑問、IDENTITYが桁あふれしたらどうなるの?
0022デフォルトの名無しさんNGNG
とりあえず、ウェブ用のアクセスカウンタから作ったら?
00231NGNG
>18
なるほど。超納得。

>19
おお。その常套手段っての知りたかった。
超基本っぽいことだから普通はどうやるのかなって。
IDENTITY関連調べてみます。

でもどっちがいいのかな。
つーか両方知ってなきゃいけないのか。

それともういっこ微妙に関係ないんですけど
普通、エラーがおきたときの情報なんかはどうするんですか
それ用のテーブルに残すとしたらどんな情報を残しとくんすか、常識的には。
0024デフォルトの名無しさんNGNG
>>23
私はローカルなファイルに書き出してますね。エラー時には、そもそも DB への
接続が死んでる可能性があるから DB には書きません。

内容は

1 タイムスタンプ
2 エラーを起こしたファイルの名前と行 (Internal Server Error の場合には
 ASPError あたりから拾える)
3 Query String を含んだ URL と POST データ
4 SQL 文を動的に作っている場合には動的に作っている SQL 文の全体、
 ストアドプロシージャ呼び出しの場合には、ストアドプロシージャ名とパ
 ラメタの値

あたりを列挙。後でユーザから報告が上がってきたときに、どこが原因なのか
突き止められる程度に情報を残しておけば良いです。
0025Error401NGNG
>でもどっちがいいのかな。

IDENTITYは、トランザクションをロールバックしたときに、元の値に
戻るのかな?

だとすれば、どちらも一緒。

違う場合(多分こっちだと思うけど)、

・連番の保証をするなら、SELECTでロックかけ、自前で番号を振る
・連番保証の必要が無い場合は、IDENTITYを使う

という使い分けをします。

>普通、エラーがおきたときの情報なんかはどうするんですか

トランザクションの内部で、自立したトランザクション(外側のトランザクション
に関係なく、コミット・ロールバックできるトランザクション)が使えない場合は、
テーブルにエラー情報を吐き出すことは出来ません。SQL Serverで使えるか
どうかは知りませんけど。

>それ用のテーブルに残すとしたらどんな情報を残しとくんすか、常識的には。

常識的には、自分が知りたいものを残しておきます、フツー(藁
0026デフォルトの名無しさんNGNG
自分の場合、こういう処理を行う場合には一度キュー
に書いた後テーブルに書くという手段をとっている
のですが、こういう事をするのは少数派なのかな?
00272NGNG
これでいいんじゃないの?

CON.BeginTrans()

On Error Goto DBError

SQL = "SELECT MAX(bk_id) max_id FROM book"
SET RS = CON.Execute(SQL)

If RS.EOF Then
newbk_id = 1
Else
newbk_id = RS("max_id")+1
End If

rs.close

Conn.Execute "Insert into book (bk_id) values (' + newbk_id + ")"

CON.CommitTrans
Exit Sub

DBError:
CON.RollBackTrans
response.redirect "error.asp"
end if
0028デフォルトの名無しさんNGNG
あの、便乗質問なんですけどSQL文を実行してループさしてテーブル形式で出力すると
出力位置が大きく下に出力するんですけど。
なにか心当たりあるひと教えてくれません
0029デフォルトの名無しさんNGNG
>>27
スバラシイ!
さすが、VB使い!
0030デフォルトの名無しさんNGNG
>>27
これ以上、恥をさらさないほうがいいよ。馬路で。
0031デフォルトの名無しさんNGNG
>>27が使ってるサーバはCode Red対策をしていないに一票。
いいかげん、ウザイんだよ。
0032デフォルトの名無しさんNGNG
>>27
ASP の VBScript には On Error GoTo ないぞ(正確には On Error GoTo 0,
On Error Resume Next だけ使える)。
003327NGNG
>>32
そーか、ごめん。
IIS+ASPにはもう何年も触ってないのに適当なこと書いちゃった。

そうすると、この場合のエラー処理はどうするんだろう。On Error Resume Next
しておいて、ConnectionオブジェクトのErrorsプロパティをチェックすれば
できるのかな?
0034デフォルトの名無しさんNGNG
>>27
つーか、そんな枝葉じゃなくて、根本的なトコロが間違ってるのよ。
0035デフォルトの名無しさんNGNG
>>27
まじで、痛すぎるよ。
カキコ控えてROMに徹したほうが身のためだよ。
00361NGNG
ええとIDENTITYで重複だめにするとエラーに行くんすよね。
先の処理をまっててくれる⇒ROCKにしたい所存。

↑こういう現状です。

>9
で言ってるようにWITH(UPDLOCK)ってのがつかえないんすが・・・

>Error401
調べ中に見つけたんすけどロールバックされたら@@IDENTITYは元に戻らないそうです。
0037デフォルトの名無しさんNGNG
なぜ、別に一個 1rowだけのMAXナンバーを格納したTableを作らんの?
そして、そのtableに
update dummy set dummy = max + 1 where dummy = max;
としてupdateを発行しないの?他のセッションが同時期におなじupdate文を
発行したなら、dummy = maxのrowは存在しなくなるから変更は行われない。
変更行がなかった場合にはもう一度maxの取得をして変更できるまで繰り返す。
update文の実行はアトミックであることが保証されてるからmax + 1もアトミックな
値が返るぞ。
0038デフォルトの名無しさんNGNG
>>30 >>31 >>35
人を批判する前に自分の考えを示しな。
27の方が多少なりともえらいぞ。
ちなみにおれはASP使いじゃないからしらん。
0039Error401NGNG
>>37
うーん、update dummy文が1文1トランザクションだとすると、連番保証が
なくなるので、IDENTITYを使うのと変わらないし、
更新対象のupdate文と同じトランザクションに入れるのなら、
select max(id) + 1と変わらない気がするんですけど・・・。

>>38
えーと、>>27のコードは、本質的に>>1となんら変わりがないので
批判されてるんじゃないかと思います。
解決法はすでに出尽くしてると思うし。
0040デフォルトの名無しさんNGNG
>>38
ASPとかそうゆう問題じゃないんだよ。
それがわからないお前も逝ってよし。
さすが、Error401氏
004127NGNG
27です。あがってたとは知りませんでした。

>>39
すいません。後で気が付いたんですが、その通りですね。

それで、昔同じようなことをしたのを思い出しました。そのときの解決法は
>>37のようにMAX値保存テーブルを作成し、ストアドプロシージャで

1. 現在のMAX値を更新用カーソルで取得する
2. インクリメントしたMAX値をそのカーソルを使用して更新する

を1トランザクションで実行し、成功したら更新したMAX値を返す、という方法を
取りました。
00421NGNG
>37 >39 >27
ありがとうございます。なんとなくわかってきたかも。
発番用のテーブルを用意するってのはそういうことだったんすね。13

そんで今わからないのがその重複しちゃ困るIDで同時に
もう1個のテーブルをINSERTまたはUPDATEしてるんですけど
「そのカーソルだと複数にまたがったトランザクションできません」
みたいなエラーがでます。
(ロックタイプ、カーソルは 3.3 です。)
0043便乗質問でスミマセンNGNG
すみません。SELECT文で教えてください。
ある条件で、11件目から5件のレコードを取り出す場合、

PostgreSQLなら
SELECT * FROM syohin WHERE tanka < 10000 OFFSET 10 LIMIT 5;

MySQLなら、
SELECT * FROM syohin WHERE tanka < 10000 LIMIT 10,5;

MS-SQL2000Serverの場合はどうしたらいいんでしょう?
いろいろ調べたのですが分かりません。
0044便乗質問でスミマセンNGNG
すみません。SELECT文で教えてください。
ある条件で、11件目から5件のレコードを取り出す場合、

PostgreSQLなら
SELECT * FROM syohin WHERE tanka < 10000 OFFSET 10 LIMIT 5;

MySQLなら、
SELECT * FROM syohin WHERE tanka < 10000 LIMIT 10,5;

Oracle8の場合はどうしたらいいんでしょう?
いろいろ調べたのですが分かりません。
ちなみに、
WHERE ROWNUM BETWEEN 11 AND 15
はダメでした。
0045 NGNG
おお!祭りか?
マルチスレッド知らない厨房がサーバーサイドやっちまったのかWW
0046 NGNG
セマフォで合ってるよん。
0047 NGNG
SELECT * FOR UPDATE //ロック セマフォね
〜
UPDATE //ロック解除
だったような気がする。
0048 NGNG
COMMITだったような気もする。
0049デフォルトの名無しさんNGNG
>19
2 INSERT 直後に @@IDENTITY で番号を取得。
のコードの書き方教えて
0050 NGNG
>>44,45
WHERE ... AND (ROWNUM >10) AND (ROWNUM <16)
だったような気がする。
0051デフォルトの名無しさんNGNG
37
もうちょっとくわしく説明して下さい

なぜ、別に一個 1rowだけのMAXナンバーを格納したTableを作らんの?
そして、そのtableに
update dummy set dummy = max + 1 where dummy = max;
としてupdateを発行しないの?他のセッションが同時期におなじupdate文を
発行したなら、dummy = maxのrowは存在しなくなるから変更は行われない。
変更行がなかった場合にはもう一度maxの取得をして変更できるまで繰り返す。
update文の実行はアトミックであることが保証されてるからmax + 1もアトミックな
値が返るぞ。
005243NGNG
>45
それはつまり、マルチスレッドというもので解決できるのでしょうか?
ありがとうございます。後は自分で調べてみます。
0053デフォルトの名無しさんNGNG
MS SQL Serverなら、フィールドの属性にindex(1,1)とか指定すれば同じ様な連番は振れたはず。
しかし、落とし穴があって、システムが異常終了するとこの連番が狂うので、DBCCで修正してやらないといけない
0054デフォルトの名無しさんNGNG
37
他のセッションが同時期におなじupdate文を
発行したなら、dummy = maxのrowは存在しなくなるから変更は行われない

この理由がよくわからないのですが
005527NGNG
>>50
Oracle の ROWNUM って、使える演算子は <, <= の二つだけだぜ。
0056仕様書無しさんNGNG
>>54

ふたつのセッションで同じ値の max (仮に10とする) を取得してしまったとしても、
そのうちのひとつのセッションで、先に dummy = max + 1 ( dummy = 10 + 1 ) に
書き換えれば、dummy = 10 の行は存在しなくなるでしょ。
もうひとつのセッションで dummy = 10 の行への書き込みを試みても、そんな行は
もうないから、書き込みは行えないというわけ。
0057デフォルトの名無しさんNGNG
すみません、教えてください。ASPからMSDEにアクセスしているのですが、DBのデータが大量にあり、
複数のクライアントからアクセスすると、「容量を超えていたため、
新規トランザクションを作成できません。」というエラーが返ってきます。
そこで、もしMSDEのトランザクション領域(デフォルト)のサイズをご存知の方が
いらっしゃいましたら、教えていただけないでしょうか?
よろしくお願いします。
0058縞栗鼠(シマリス)の親方NGNG
中央高等学院の従業員「西部」が女子トイレに闖入し
汚物入れを物色して女子生徒の使用済みタンポン・ナプキン等を
個人的に収集しているというのは事実。

中央高等学院では
生徒及びその保護者の個人情報を名簿図書館等に売却しています。
中央高等学院 http://chs-f.com/index.html 中央高等学院福岡校
http://www.chuo-school.ac/
0059名無しさん@EmacsNGNG
>>44
FAQなのレス。

(>50だと期待する結果にならんと思われ。
WHERE句のROWNUMは条件が入るとみんな偽になってしまうので。)

11件目から5件ってことは11件目から15件目を取得すればいいので、

SELECT * FROM syohin WHERE tanka < 10000 AND ROWNUM <= 15
MINUS
SELECT * FROM syohin WHERE tanka < 10000 AND ROWNUM < 11

つまり、15件以下のデータから10件以下をマイナスすればいいのレス。
0060名無しさん@EmacsNGNG
ごめん。ageてしまった...

あと、間違ってた...鬱だ...

最後は ROWNUM < 11 ですわな...
0061デフォルトの名無しさんNGNG
こういうのは、MAX+1を取ってくるんじゃなくて、SELECTのタイミングで
MAX+1に適当なダミーデータを放り込んでおいて、INSERTの変わりにUPDATE
すべきかと思われ。

SQL*Serverにあるのかどうか知らんが、SWQENCEつかうのが一番いいとおもわれ。
0062デフォルトの名無しさんNGNG
>>1
方法1.1〜3をBEGIN TRANSACTIONとCOMMITで囲む。(これにより排他がかかる)
方法2.ユニークインデックスにして、重複エラー時は適当な時間をおいてリトライさせる。
方法3.1〜2を3のトリガー処理に書きSQL分を一つにする。トリガー処理は一意であるのでOKになる。

■ このスレッドは過去ログ倉庫に格納されています