0

ユーザーがデータベースで何かを実行し(重要ではありません)、サードパーティのソフトウェアを使用して通知を送信できるようにするアプリケーションがあります(selectステートメントでのみDBからデータを読み取ることができます)。
通知は2つのSQLストアドプロシージャによって処理されます。1つ目は通知テーブルに通知を追加し多くの通知は送信前に追加できます)、2つ目は次のコードを使用して通知を送信します。

EXEC master.dbo.xp_cmdshell 'c:\send.cmd';  
WAITFOR DELAY '00:00:05';  
DELETE FROM Notifications;

問題は2番目の手順にあります。2人のユーザーが同時にメッセージの送信をトリガーし、通知テーブルのコンテンツ全体が受信者に2回送信される場合があります。

通知テーブルをロックするようなものを使用したかったのですが、コードの最初の行で実行されるサードパーティのソフトウェアでテーブルを読み取ることができるはずです。言い換えれば、SendMsgプロシージャのインスタンスを一度に1つだけ実行できるようにしたい(他のプロシージャではNotificationsテーブルを読み取り専用のままにしておきます)。
どうすればこれを達成できますか?

4

3 に答える 3

2

を使用sp_getapplocksp_releaseapplockて、自分自身にmutex。詳細については、SQL Server 2005のアプリケーションロック(またはミューテックス)を参照してください。外部コマンドが十分に高速であれば、2番目のプロセスがロックを取得するのを待つことに害はありません。WAITFOR DELAYすべてがアプリのロックによって管理されるため、を削除することもできます。

于 2013-03-06T18:14:03.560 に答える
0

通知テーブルにフィールドを追加して、通知のバッチを示す必要があります。

create table notifications(
   batchID uniqueidentifier,
   text nvarchar(max)

)。

したがってAddNotification、バッチである追加のGUIDパラメーターを取ります。次に、送信プロシージャはこれを行う必要があります。

declare @cmd nvarchar(max)
set @cmd = 'c:\send.cmd' + ' ' + cast(@batchID as varchar(36))

EXEC master.dbo.xp_cmdshell @cmd;  
--- WAITFOR DELAY '00:00:05';  
-- What are you waiting for? xp_cmdshell waits already.
-- You can also induce CMD.exe to wait for windows programs using the START /WAIT option
DELETE FROM Notifications where batchID = @batchID;

明らかに、send.cmdは、パラメーターと同じGUIDを取得するように変更する必要があります。

ストアドプロシージャにパラメータを追加できない場合は、SPID(@@spid)を使用しても機能する可能性があります。ただし、すべてのストアドプロシージャに(接続プールからの)同じ基になる接続が使用されていることを確認できます。ただし、パラメータを追加する必要がsend.cmdあります。

于 2013-03-06T12:18:06.907 に答える
0

編集: MSSQL2005以降の方がはるかに優れているDavidTMacknetの回答を参照してください。ただし、この手法は、他のDBMSまたは以前のバージョンのSQLサーバーでも役立つ場合があります。

2番目のテーブルを使用してこれを実行し、テーブルがロックされているかどうかの事実を保持できます。

create table Notifications_Lock(
ix int primary key,
fLocked bit,
constraint Notifications_Lock_SingleRow check (ix = 1)
)

insert Notifications_Lock values( 1, 0, null)

go

create proc NotificationsLockTry
as
begin
    update Notifications_Lock set fLocked = 1 
    from Notifications_Lock with (TABLOCKX)
    where fLocked = 0
    return @@rowcount
end
go
create proc NotificationsLockTimeout( @waitSeconds int)
as
begin
    set @waitSeconds = isnull(@waitSeconds, 0)

    declare @dtWaitTill datetime
    set @dtWaitTill = dateadd(second, @waitSeconds, getutcdate())
    declare @fLocked int

    update Notifications_Lock set fLocked = 1 
    from Notifications_Lock with (TABLOCKX)
    where fLocked = 0
    set @fLocked = @@rowcount

    if @fLocked > 0  return @fLocked

    while @fLocked = 0 And @dtWaitTill > getutcdate()
    begin
        waitfor delay '00:00:01'
        update Notifications_Lock set fLocked = 1 
        from Notifications_Lock with (TABLOCKX)
        where fLocked = 0
        set @fLocked = @@rowcount
        if @fLocked > 0 return @fLocked
    end
    return @fLocked
end
go
create proc NotificationsUnlock
as
begin 
    update Notifications_Lock set fLocked = 0, dtLocked = null
    from Notifications_Lock with (TABLOCKX)
    where fLocked = 1
    return @@rowcount
end

使用例:

declare @fLocked int
-- Wait up to 5 minutes for a lock
exec @fLocked = NotificationsLockTry 300
if @fLocked = 0 
begin
    raiserror('Unable to lock notifications table', 11,11)
    return
end
-- Locked OK
-- INSERT NOTIFICATIONS HERE
-- CALL Send.cmd HERE
exec NotificationsUnlock

return

この方法を使用している場合、ジョブが終了または中断された場合は、通知テーブルのロックを手動で解除する必要がある場合があることに注意してください。

ロックされた日付をテーブルに追加してタイムアウトを追加することもできます。これを定期的に確認し、過去に遠すぎる場合は、ロックを解除するだけです。

于 2013-03-06T14:28:14.030 に答える