336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Learn more about SQL Server tools 

 

Tutorials          DBA          Dev          BI          Career          Categories          Events          Whitepapers         Today's Tip          Join

 

Prevent multiple users from running the same SQL Server stored procedure at the same time


By:    |   Read Comments (17)   |   Related Tips: More > Locking and Blocking

Problem

There is a stored procedure that must be run by only one user at a time. How do to I prevent multiple users from running the stored procedure at the same time?

Solution

For years I've implemented home grown solutions using a "lock" table but this always had inherent problems with either faulty code or with failures that didn't clean up the lock table. Invariably there were situations where a process died and the "lock" hung around preventing other users from running the protected code.  It was always necessary to have a "Clean up the bad locks" feature as part of the application.

A better solution is available: SQL Server provides an application manageable lock mechanism through the sp_getapplock / sp_releaseapplock pair of system stored procedures. They provide a way for application code to use SQL's underlying locking mechanism, without having to lock database rows. The lock can be tied to a transaction or session ensuring lock release when the transaction COMMITs or ROLLSBACK or when the session exits and the connection is closed.

Using sp_getapplock to lock a resource

To obtain a lock, call sp_getapplock as follows:

DECLARE @RC INT
Begin tran
Exec @RC =sp_getapplock @Resource='MyLock', @LockMode='Exclusive'
            , @LockOwner='Transaction', @LockTimeout = 15000
SELECT @@SPID [session_id], @RC [return code], GETDATE()
waitfor delay '00:00:08'            
commit

The sp_getapplock call takes out a lock to the resource "MyLock" and holds it as long as the transaction is alive. In this case it will wait 8 seconds and then execute the COMMIT, which will release the lock. To see how it works open a Management Studio session and executed the code above, then quickly open another Management Studio window and execute this same code again.  That's what I've done in this picture:

sp_getapplock running in two windows with one waiting on the other Img

The session at the top was started first and immediately returned the resultset with the time as the last column. Then as quickly as I could I started the second session and you can see that it's executing the query. Once it's completed you'll see something like the next picture where both sessions have now completed:

sp_getapplock both sessions complete

Notice that the return code for the first query is zero, which means that the lock was granted right away. The return code for the second session is one, which means that the lock was granted after waiting for another session that held the lock. There are also negative return codes such as minus one which indicates that the lock could not be granted by the timeout and other negative codes for other error situations. You should also notice the time. Session 2 returned it's result set eight seconds after session 1. That's because it had to wait those eight seconds to acquire the lock.

Once the lock is acquired releasing it depends on the @LockOwner parameter. If @LockOwner is "Session" the lock is held until it is explicitly released with a call to sp_releaseapplock or if the session ends. A session ends when the connection is closed that can be somewhat risky if the caller has a tendency to hold onto connections. If @LockOwner is "Transaction" the lock is released either with a call to sp_releaseapplock or when the transaction is committed or rolled back. Coding tends to be easier when @LockOwner is Transaction so I try and use it whenever possible but, of course, @LockOnwer='Transaction' can only be used inside a user transaction.

Using sp_releasapplock to release a resource

The call to sp_releaseapplock requires the @Resource and @LockOwner parameters and looks like this:

DECLARE @RC INT
Exec @RC = sp_releaseapplock @Resource='MyLock', @LockOwner='Transaction'
select @RC

However, if @LockOwner='Transaction' then sp_releaseapplock must be executed inside the transaction. In addition, if the lock isn't held by the transaction SQL Server doesn't just return a return code, an explicit error is thrown and the code must account for that possibility. For that reason when using @LockOwner='Transaction' I avoid calling sp_releaseapplock but instead rely on the transaction COMMIT to release the lock.

Stored Procedure Example Using sp_getapplock

Now take a look at how to use sp_getapplock in a stored procedure.  The sample procedure, critical_section_worker, includes transaction control, messaging, and error handling typical of a real world procedure that uses sp_getapplock.  Here's a script that creates it as a permanent procedure in tempdb.

USE [tempdb]
GO
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
GO
CREATE PROC dbo.critical_section_worker  @wait_duration varchar(30) = '00:01:00' -- default one minute
/* Performs a task in a critical section of code that can only be run
   by one session at a time. The task is simulated by a WAIT  */
AS
declare @rc int = 0 -- return code
      , @msg varchar(2000)
set @msg = convert(varchar,getdate(), 114) + ' critical_section_worker starting'
raiserror (@msg, 0, 1) with nowait 
Begin Try
 Begin tran
 set @msg= convert(varchar,getdate(), 114) + ' requesting lock'
 raiserror (@msg, 0, 1) with nowait
 Exec @rc = sp_getapplock @Resource='CriticalSectionWorker' -- the resource to be locked
         , @LockMode='Exclusive'  -- Type of lock
         , @LockOwner='Transaction' -- Transaction or Session
         , @LockTimeout = 15000 -- timeout in milliseconds, 15 seconds
                            
 set @msg= convert(varchar,getdate(), 114) + ' sp_getapplock returned ' + convert(varchar(30), @rc) + ' -- '
      + case when @rc < 0 then 'Could not obtain the lock'  else 'Lock obtained'  end
 raiserror (@msg, 0, 1) with nowait
  
 if @rc >= 0 begin
  set @msg= convert(varchar,getdate(), 114) + ' got lock starting critical work '
  raiserror (@msg, 0, 1) with nowait
  
  waitfor delay @wait_duration -- Critical Work simulated by waiting
  
  commit tran -- will release the lock
  set @msg= convert(varchar,getdate(), 114) + ' work complete released lock' 
  raiserror (@msg, 0, 1) with nowait
  end 
 else begin
        
  rollback tran
  set @rc = 50000
 end
end try
begin catch
 
 set @msg = 'ERROR: ' + ERROR_MESSAGE() + ' at ' 
            + coalesce(ERROR_PROCEDURE(), '')
            + coalesce (' line:' + convert(varchar(30), ERROR_LINE()), '')
            
 RAISERROR (@msg, 0, 1) with nowait -- ensure the message gets out                                 
 if @@Trancount > 1 rollback tran
 raiserror (@msg, 16, 1)
 end catch
 return @rc
 GO

The lock is held for the duration of the transaction and will be released either by the COMMIT at the end of the TRY block or by the ROLLBACK in the CATCH block.  You might notice the use of RAIERROR... WITH NOWAIT instead of PRINT.  RAISERROR with a error code of zero isn't really an error and adding the WITH NOWAIT forces the message and any preceding messages to be displayed immediately.  I described this feature in detail in the this tip Using the NOWAIT option with the SQL Server RAISERROR statement.

This next picture shows how I ran the query in two windows.  The upper window, with session 56, was started first, at 19:15.14.413.  The lower window, with session 53, was started 5 seconds later.  The upper window grabbed the lock first and held it for 30 seconds.  Since the timeout on the call to sp_getapplock is only 15 seconds, the lower session never got the lock and sp_getapplock returned -1.  

sp_getapplock running in two windows with one waiting on the other Img

In this last picture I ran critical_section_worker with a runtime of 10 seconds.  This time the lower session got the lock before the timeout and was able to complete it's work but only after waiting for the upper session to complete.

sp_getapplock running in two windows with one waiting on the other Img

That's exactly what I wanted: only one instance of the stored procedure can enter the critical section, protected by the sp_getapplock call, at a time.  It's always better to write code that allows multiple instance to run at the same time, but when that's impossible, or just would take too much effort, I use sp_getapplock to ensure that critical sections are single threaded.

Next Steps
  • Locate any places where you've implemented similar functionality using rows in a lock table and replace them with the more robust sp_getapplock.
  • Check your code for sections that must be single threaded.  These are usually in long running batch routines.  If these are not protected from having multiple instances, add the necessary calls to sp_getapplock to add robustness to your code.


Last Update:  





About the author
MSSQLTips author Andy NovickAndy Novick is a SQL Server Developer in the Boston area with 25 years of database and application development experience. 

View all my tips




More SQL Server Solutions




Post a comment or let the author know this tip helped.

All comments are reviewed, so stay on subject or we may delete your comment. Note: your email address is not published. Required fields are marked with an asterisk (*).

*Name    *Email    Notify for updates 


Get free SQL tips: 

*Enter Code   refresh code    



Monday, February 01, 2016 - 2:44:09 PM - joelyBack To Top

 thanks for this post.

 

 


Wednesday, May 27, 2015 - 2:54:53 PM - ClaudioBack To Top

Hi Alain,

I think you forgot to add @LockOwner (Session or Transaction).

Have you checked that?


Tuesday, May 12, 2015 - 2:31:27 PM - AlainBack To Top

1. At '13:00:00' WebServerA send this query to DbServer
begin transaction;
  WaitFor time '13:05:00'
  declare @id int;
  exec @id = sp_getapplock @Resource = 'r1', @LockMode = 'Exclusive'; -- I've tried all modes
  if (@id in (0,1)) begin
    Waitfor delay '00:00:10'; -- hold lock for 10 sec
    exec sp_releaseapplock @Resource = 'r1';

    commit transaction;
  end else rollback transaction;

2. At '13:01:00', WebServerB send the same query to dbserver. 

3. BUG: both sp_getapplock gave 0 at once! No query had to wait 10 sec

Please help me.  Thanks


Friday, July 25, 2014 - 1:29:09 AM - Andy NovickBack To Top

Regarding:

So does @Resource hold the name of the stored procedure. ex: Resource = 'MyLock' , is Mylock a stored procedure.

.......

You could use the name of the stored procedure as @Resource.  That would work. The procedures would start but would block until they could get the lock.  



Friday, July 25, 2014 - 1:26:48 AM - Andy NovickBack To Top

Regarding the version of "Query Analyzer".  That's SSMS from SQL 2008 R2 from roughly 2010.  It's not that old.


Friday, July 25, 2014 - 1:24:39 AM - Andy NovickBack To Top

Regarding 

I have SP name like usp_EPMCALL. how can i use this lock machanisum sp_getapplock at user stored procedure? one of my OLTP database frequently afftected blocking issues.

is it like as below?......

 

sp_getapplock is a way to use a lock to block all but one procedure from owning a resource.  So as long as you only have blocking and not deadlocking there's no need to add additional locks.  SQL Server is already serializing the procedures.  There's nothing to be gained by introducing more locks.

 


Thursday, July 24, 2014 - 1:11:42 AM - anandaBack To Top

I have SP name like usp_EPMCALL. how can i use this lock machanisum sp_getapplock at user stored procedure? one of my OLTP database frequently afftected blocking issues.

is it like as below?

 

DECLARE

 

@RC INT

Begin tran

Exec @RC =sp_getapplock@Resource='usp_EPMCALL', @LockMode='Exclusive'

, @LockOwner='Transaction', @LockTimeout = 15000

SELECT @@SPID [session_id], @RC [return code],GETDATE()

waitfor delay'00:00:08'

commit

 

Thanks

 


Wednesday, July 09, 2014 - 9:35:13 AM - Mark TierneyBack To Top

Thanks Andy.

I have used this previously to lock a range of related rows, via common resource name, but have never thought about single threading a procedure with it. Always great to learn about new ways a feature can be used.


Tuesday, April 29, 2014 - 10:00:08 AM - chiragBack To Top

Hi  all

Basic doubts..

AS i understand this is for preventing same stored procedure from being executed by different users at the same time.

So does @Resource hold the name of the stored procedure. ex: Resource = 'MyLock' , is Mylock a stored procedure.

 

 

 

 


Monday, April 28, 2014 - 8:41:39 AM - Divine FlameBack To Top

Nice Article Andy. Thanks for sharing.


Friday, April 25, 2014 - 2:30:56 AM - Rsotand AbearBack To Top

Nice article, I get to learn something I may need in the future.


Thursday, April 24, 2014 - 1:19:40 PM - Larry SmithBack To Top

I've used this in the past to good effect in many places. That this method utilized SQL Server's own lock mechanism it a life saver.  This means your locks are automatically released if, say, your session is terminated abnormally by loss of connection or any other means, just like any table lock would be if you were to die mid-transaction.  I think of this as a MUTEX within sql.  In fact, we've even used this just as a mutex mechanism where we may have a pool of processes on separate systems, where tasks are mutually exclusive.

Good article!


Thursday, April 24, 2014 - 7:59:16 AM - Steve JohnsonBack To Top

I have a couple of processes that spradically cause deadlocks - I'll be investigating the use of these system stored procs. as a means of preventing those. Thanks a lot for this, much appreciated.


Thursday, April 24, 2014 - 4:36:02 AM - WillemBack To Top

Great article. Is that Query Analyzer you are using? Looks retro,

I suddenly felt 10 years younger when I saw the screenshots :-) 


Saturday, April 12, 2014 - 1:57:58 AM - David SBack To Top

Excellent article.  Look forward to reading more!  I even learned the trick of using RAISERROR for messages.


Friday, April 11, 2014 - 5:40:44 PM - JustinBack To Top

I just ran into a situation today where I needed this. Great article.


Thursday, April 10, 2014 - 11:36:40 AM - Wanderlei SantosBack To Top

I'm certainly adding this to my toolbelt. Thanks for the code.


Learn more about SQL Server tools


336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

Boost 빌드는 잘 됐다고 치고 


이제 테스트 프레임워크를 돌려 보자.


이런 건 Adapter가 있으면 받는 게 짱임 


Boost Unit Test Adapter 페이지 가서 다운로드


https://visualstudiogallery.msdn.microsoft.com/5f4ae1bd-b769-410e-8238-fb30beda987f


설치 후 VS 재시작.


새 프로젝트 -> C++ -> Test -> Boost Unit Test Project 선택




프로젝트 생성 후 


프로젝트 속성 -> 플랫폼 도구 집합 -> 툴 선택 (기본은 선택되어 있지 않음)




include와 lib 경로 추가 


추가 -> 새 항목 -> C++ -> 파일명


전 ChatCtrl를 테스트하기 위해 ChatCtrlTest로 이름 정해서 생성





클래스 생성하고 체크 함수 물어 주고 실행



요렇게 테스트 프레임워크 구형 완료!


만약 빌드를 BOOST_TEST_NO_MAIN으로 했을 경우 아래와 같이 메인과 초기화 시점을 잡을 수 있음


boost::unit_test::test_suite *init_function(int argc, char *argv[])

{

    // create test cases and suites and return a pointer to any enclosing

    // suite, or 0.

    return 0;

}


int main(int argc, char *argv[])

{

//라이브러리 로딩

boost::unit_test::unit_test_main(init_function, argc, argv);

//라이브러리 해제

}


테스트 해보니 static 기반의 싱글톤의 경우 boost가 메모리 릭으로 인식하는데 아직 해결책 못찾음 


그래서 걍 메모리 릭 체크하는 기능 끔 ㅜㅜ


프로젝트 속성 -> 디버깅 -> 명령 인수 -> --detect_memory_leak=0 추가


간단하게 옵션 설명 하자면 


--detect_memory_leak=0 (체크 하지 않음)

--detect_memory_leak=1 (체크)

--detect_memory_leak=특정 숫자 (해당 메모리가 잡힐때  디버깅 모드라면 브레이크 걸림 단 Visual Studio만 가능)


빌드시 메이븐처럼 유닛 테스트 걸고 싶으면


속성 -> 빌드 이벤트 -> 빌드 후 이벤트 -> 명령줄 -> "$(TargetDir)\$(TargetName).exe" --result_code=no --report_level=no --detect_memory_leak=0 이런식으로 걸어주면 빌드 시 자동으로 유닛 테스트 수행하게 됨


이제 노가다 시작






' > boost' 카테고리의 다른 글

Boost 1.60 빌드하기  (0) 2016.04.29
boost vs2010에서 사용하기  (0) 2012.03.20
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


Get Boost


C++ 서버를 만들 일이 없을 줄 알았더니 먹고살려니 할 일이 생겼다.


옛날에는 유닛 테스트 같은 거 안 돌렸는데 


이제 머리가 나빠지다 보니 돌려야겠다 


테스트하는 방법은 gtest, boost 등이 있는데 


MS C++ REST SDK 보니깐 Boost로 되어 있어서 나도 Boost로 설정해 본다


우선 Boost가 필요하니 다운 받는다. 


1.60 버전이 최신이다 


http://www.boost.org/users/history/version_1_60_0.html 


난 zip 파일 버전을 받았다


http://sourceforge.net/projects/boost/files/boost/1.60.0/boost_1_60_0.zip/download 


압축 풀고 


bootstrap.bat 실행 시키면 b2.exe가 나온다 b2.exe가 있다면 bat파일 실행 안시켜도 무방!


빌드를 할려면 cl이란 놈이 필요한데 VisualStudio bin 폴더에 들어 있다


VisualStudio 2015 기준으로 설명하자면 


C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin 안에 들어 있다


해당 경로를 Path에 추가 시켜준다.


빌드는 몇가지 옵션이 있는데 


아래와 같이 사용했다.


b2 -j4 -a --toolset=msvc-14.0 variant=debug,release link=static threading=multi address-model=64 stage


유니테스트만 할꺼면

b2 -j4 -a --with-test --toolset=msvc-11.0 variant=debug,release link=static threading=multi address-model=64 stage define=BOOST_TEST_NO_MAIN 


오래 시간이 지난 후 stage/lib 안에보면 lib 파일이 있다.


' > boost' 카테고리의 다른 글

Boost Unit Test Framework For VisualStudio  (0) 2016.04.29
boost vs2010에서 사용하기  (0) 2012.03.20

+ Recent posts