select * from t_order_no

 

-- t_order_no

1

 

 

update t_order_no
set t_order_no=2
output inserted.t_order_no new
     , deleted.t_order_no before

 

-- new     old

         2       1

 

 

'MSSQL' 카테고리의 다른 글

로그파일 축소  (0) 2012.07.22
with(updlock) 케이스별 테스트  (0) 2012.01.26
MSSQL identity 테스트  (0) 2012.01.20
(펌) MSSQL Identity 자동 증가함수  (0) 2012.01.20
(펌) T_SQL 같은 그룹의 데이터를 붙여서 출력하기  (0) 2011.12.27

--1. 로그 파일 크기 확인(모든 데이터베이스에서 트랜잭션 로그 공간의 사용에 관한 통계를 제공)
DBCC SQLPERF(LOGSPACE)

 

--2. 로그 데이터 지우기
BACKUP LOG dfo01 WITH NO_LOG

 

--3. 로그 파일 크기 줄이기
DBCC SHRINKFILE(dfo01_Log, 10)

 

--4. 로그 파일 크기 확인
DBCC SQLPERF(LOGSPACE)


with(updlock) 케이스별 테스트

BEGIN TRANSACTION 과 함께 사용하여야 함.

FU_CALCSET
-------------------------------------------------
key_no | name  | status
-------------------------------------------------
1        aaa     1  
2        bbb     1
3        ccc     1
4        ddd     1

----------------------------------------------------------------------
-- case 1 : 세션1 검색결과set의 모든 row에 updlock이 걸린다.
--              다건 vs 단건
----------------------------------------------------------------------
-- 세션1
BEGIN TRANSACTION;
select * from FU_CALCSET with(updlock) where status = '1';
sleep(30);
cursor.update();

-- 세션2-1
select * from FU_CALCSET where key_no = 1;
sleep(5);
cursor.update();
===>
실패 : 세션1의 지연시간동안 세션1 검색결과set의 모든 레코드를 update 할 수 없음.

-- 세션2-2
select * from FU_CALCSET with(updlock) where key_no = 2;
===>
실패 : 세션1의 지연시간동안 lock 걸림.

-- 세션2-3
BEGIN TRANSACTION;
select * from FU_CALCSET with(updlock) where key_no = 3;
===>
실패 : 세션1의 지연시간동안 lock 걸림.

----------------------------------------------------------------------
-- case 2 : BEGIN TRANSCATION 없이 사용한 updlock은 작동안함.
--              다건 vs 단건
----------------------------------------------------------------------
-- 세션1
select * from FU_CALCSET with(updlock) where status = '1';
sleep(30);
cursor.update();

-- 세션2-1
select * from FU_CALCSET where key_no = 1;
sleep(5);
cursor.update();
===> 성공

-- 세션2-2
select * from FU_CALCSET with(updlock) where key_no = 2;
sleep(5);
cursor.update();
===> 성공

-- 세션2-3
BEGIN TRANSACTION;
select * from FU_CALCSET with(updlock) where key_no = 3;
sleep(5);
cursor.update();
COMMIT TRANSACTION;
===> 성공

----------------------------------------------------------------------
-- case 3 : 단건 vs 다건
----------------------------------------------------------------------
-- 세션1
BEGIN TRANSACTION;
select * from FU_CALCSET with(updlock) where key_no = 3;
sleep(25);
cursor.update();

-- 세션2-1
select * from FU_CALCSET where status = '1';
sleep(5);
cursor.update();
===>
실패 : 3번 레코드를 업데이트 할때 세션1의 지연시간만큼 lock걸림

-- 세션2-2
select * from FU_CALCSET with(updlock) where status = '1';
===>
실패 : 세션1의 지연시간동안 lock 걸림.
       3번 레코드의 status가 '1'이기 때문에 검색결과set에 포함된다.


create table testdb
( a int identity(1,1)
, b varchar(10) default 'test')

-- 데이터가 없을때 idval 값은 1
select * from testdb
select idval = IDENT_CURRENT('testdb')
select isnull(max(a),0) from testdb
-- no data
-- 1
-- 0

insert into testdb default values select @@identity
-- 1
select * from testdb
-- 1 test
select idval = IDENT_CURRENT('testdb')
-- 1
select isnull(max(a),0) from testdb
-- 1

insert into testdb default values select @@identity
-- 2
select * from testdb
-- 1 test
-- 2 test
select idval = IDENT_CURRENT('testdb')
-- 2
select isnull(max(a),0) from testdb
-- 2

insert into testdb values (3,'111')
-- 메시지 8101, 수준 16, 상태 1, 줄 1
-- 테이블 'testdb'에 있는 ID 열의 명시적 값은 열 목록이 사용되고 IDENTITY_INSERT가 ON일 때만 지정할 수 있습니다.

 set identity_insert testtable on;
insert into testtable (col1, col2) select coll1, coll2 from testtable2
--insert할 컬럼을 정확히 명시해야 함
set identity_insert testtable off;


--------------------------------------------------------
@@IDENTITY는 싱글쓰레드에서만 사용해야하는 이유
--------------------------------------------------------

@@IDENTITY와 SCOPE_IDENTITY는 현재 세션의 테이블에서 생성된 마지막 ID 값을 반환합니다. 그러나, SCOPE_IDENTITY는 현재 범위 내에서만 값을 반환합니다. @@IDENTITY는 특정 범위로 제한되지 않습니다.

IDENT_CURRENT는 범위와 세션으로 제한되지 않고, 지정된 테이블로 제한됩니다. IDENT_CURRENT는 임의의 세션과 범위에 있는 특정 테이블에 생성된 ID 값을 반환합니다. 자세한 내용은 IDENT_CURRENT를 참조하십시오.

위의 설명과 같이 @@IDENTITY는 멀티스레드에서 사용할때 엉뚱한 값을 가지고 있을 가능성이 높다.
되도록 IDENT_CURRENT(‘테이블명’) 을 사용하자.


 


Identity
:오라클의 시퀀스와 비슷한 기능
------------------------------------------------------------------------


#1 기본 사용법

사용예)
<?xml:namespace prefix = p /><?xml:namespace prefix = p />
create table test(
no int identity(1,1) NOT NULL, --(시작할 숫자값, 증가할 숫자값)
name varchar(256) NULL
------------------------------------------------------------------------
#2 인서트시 증가된 값 얻어오기

Identity를 설정해준 테이블에 인서트한 경우
인서트한 identity
불러오기 위해서는 보통 다시한번 select를 해주게 된다

인서트 쿼리 뒤에 select @@Identity을 붙여 주면 insert한 row의 identity를 불러올 수 있다.

사용예)
insert into test(name) values('James') select @@Identity as 'id'  (밑줄은 별칭이므로 아무거라도 상관 없다.)

-------------------------------------------------------------------------
#3 증가값을 임의로 정해보자

:Identity를 선언한 경우에는 인서트 시 ,Identity선언 컬럼의 값임의로 지정할 수 없다
ex) insert into test(no,name) values(1000,'James')
테이블 생성 시 디폴트 값으로 임의의 번호로 지정하고 싶거나,
삭제된 번호를 채워 넣고 싶을 때 유용한 방법이다.

-
SET IDENTITY_INSERT [테이블명] ON;
  증가값을 수동 지정

-SET IDENTITY_INSERT [테이블명] OFF;
 증가값을 자동 지정 (보통 우리가 사용하는 상태 )

-DBCC CHECKIDENT ('[테이블명]', RESEED, 0);
 시작값을 임의 지정

사용예)

SET IDENTITY_INSERT test ON;                      -- Identity 증가값을 수동지정
insert test(no,name) values(99999,'admin')      --수동입력 가능
SET IDENTITY_INSERT product_lank OFF;        --Identity 증가값을 자동지정
                                                                  --※ 증가값이 최초 1-->99999 변경되어짐
                                                                       즉,다음 인서트 값 Identity는 100000 됨

DBCC CHECKIDENT ('product_lank', RESEED, 0);   -- 시작값을 0으로 지정, 다음 증가값은 1이됨.



출처 : http://www.sqlleader.com/mboard.asp?exec=view&strBoardID=SS2005TSQL&intPage=1&intCategory=0&strSearchCategory=|s_name|s_subject|&strSearchWord=&intSeq=972



자주가는 블로그에 재미있는 꺼리가 올라와서 간단히 정리해봤습니다.
 
 
다음과 같은 형태의 데이터를


다음과 같이 같은 id 그룹별로 쉼표(,)로 붙여서 출력하는 문제입니다.
 
조건은 
  a. 커서를 쓰면 안되며,
  b. 임시 테이블 또는 테이블 변수와 같은 것 사용 없이 쿼리 한 방으로 결과 뽑기
  c. 대신 SQL 2005의 CTE 등은 이용해도 되기~ 입니다.
 
아래의 여러 고수들이 제시한 방법들을 보시기 전에 먼저 한 번 고민해 보시길 바랍니다.^^
 
 
테스트용 데이터 생성하기
 

USE TEMPDB

GO

 

 

IF EXISTS (SELECT * FROM SYS.TABLES WHERE NAME = 't1' AND type = 'U')

        DROP TABLE t1

Go

 

 

CREATE TABLE t1 (id INT, NAME VARCHAR(MAX))

INSERT t1 values (1,'Jamie')

INSERT t1 values (1,'Joe')

INSERT t1 values (1,'John')

INSERT t1 values (2,'Sai')

INSERT t1 values (2,'Sam')

INSERT t1 values (3,'Roger')

INSERT t1 values (4,'Walter')

INSERT t1 values (4,'Teddy')

GO

 

SELECT * FROM T1

GO

/*

id      NAME

-----------------

1       Jamie

1       Joe

1       John

2       Sai

2       Sam

3       Roger

4       Walter

4       Teddy

*/

 
 
 
 
[방법 1]

--------------------------------------------------------------------------------------------

-- Nick Barclay

--------------------------------------------------------------------------------------------

WITH ConcatNamesCTE (id, [NAME], rn)

AS

(

        SELECT id, [NAME], rn

        FROM

        (

               SELECT id, [NAME],

                       row_number() OVER(PARTITION BY id ORDER BY id) AS rn

               FROM t1

        ) a

        WHERE rn = 1

        UNION ALL

        SELECT b.id, cn.[NAME] + ',' + b.[NAME], b.rn

        FROM

        (

               SELECT id, [NAME],

                       row_number() OVER(PARTITION BY id ORDER BY id) AS rn

               FROM t1

        ) b

        INNER JOIN ConcatNamesCTE cn ON cn.id = b.id AND cn.rn + 1 = b.rn

)

SELECT d.id, d.[NAME]

FROM

(

        SELECT MAX(rn) AS rn, id

        FROM ConcatNamesCTE

        GROUP BY id

) c

INNER JOIN ConcatNamesCTE d ON d.id = c.id AND d.rn = c.rn

ORDER BY id

GO

 
 
[방법 2]

--------------------------------------------------------------------------------------------

--Adrian Downes

--------------------------------------------------------------------------------------------

SELECT res.id, MAX(res.[NAME]) AS [NAME]

FROM

(

        SELECT c.id,

        CASE

               WHEN PATINDEX('%' + d.[NAME] + '%', c.[NAME]) = 0 AND c.id = d.id

                       THEN c.[NAME] + ', ' + d.[NAME]

               ELSE c.[NAME]

        END AS [NAME]

        FROM

        (

               SELECT a.id, MIN(a.[NAME]) AS [NAME]

               FROM

               (

                       SELECT y.id,

                       CASE

                               WHEN PATINDEX('%' + z.[NAME] + '%', y.[NAME]) = 0 AND y.id = z.id

                              THEN y.[NAME] + ', ' + z.[NAME]

                              END AS [NAME]

                       FROM t1 y

                       INNER JOIN t1 z ON y.id = z.id

               ) a

               GROUP BY a.id

        ) c

        INNER JOIN t1 d ON c.id = d.id

) res

GROUP BY res.id

GO

 
 
[방법 3]

--------------------------------------------------------------------------------------------

--Jamie Hunter

--------------------------------------------------------------------------------------------

SELECT

        DISTINCT

        id

        , STUFF(

               (SELECT ',' + name AS [text()] FROM t1 b WHERE b.id = a.id FOR XML PATH(''))

               ,  1,  1,  ''

               ) AS name_csv

FROM t1 a

ORDER BY 1

GO

 
 
[방법 4]

--------------------------------------------------------------------------------------------

--Rick R

--------------------------------------------------------------------------------------------

WITH t2 AS (

        SELECT id, MIN(name) name

        FROM t1 GROUP BY id

        UNION ALL

        SELECT a.id, a.[NAME] + ',' + b.[NAME] AS name

        FROM t1 a JOIN t2 b ON a.id = b.id AND a.[NAME] > b.[NAME]

)

SELECT id, MAX(name) FROM t2

GROUP BY id

GO

 
 
 
개인적으로는 방법 3이 좋게 보이네요..^^ 여러분은 어떤 방법이 제일 좋으신지요..
더 좋은 방법 있으면 리플 달아주세요.
 
 
select  servername, batch_id, count(*) cnt from f_pgm_info
where   biz_dt between cast(convert(varchar,getdate(),111)+' 00:00:00:000' as datetime)
                   and cast(convert(varchar,getdate(),111)+' 23:59:59:998' as datetime)
and     batch_id like '%.BATCH'
group by servername, batch_id
order by servername, batch_id
;
ebroker.2035        FO.BATCH    5
ebroker.2035        JU.BATCH    2
ebroker.2035        KP200.BATCH 1
ebroker.2035        LP.BATCH    2
ebroker.2035        MC.BATCH    2
ebroker_save.2076   FO.BATCH    5
ebroker_save.2076   JU.BATCH    2
ebroker_save.2076   KP200.BATCH 1
ebroker_save.2076   LP.BATCH    2
ebroker_save.2076   MC.BATCH    2
etr.2034            FO.BATCH    5
etr.2034            JU.BATCH    2
etr.2034            KP200.BATCH 1
etr.2034            LP.BATCH    2
etr.2034            MC.BATCH    2
etr_ord.2019        FO.BATCH    5
etr_ord.2019        JU.BATCH    2
etr_ord.2019        KP200.BATCH 1
etr_ord.2019        LP.BATCH    2
etr_ord.2019        MC.BATCH    2
etr_sch.2018        FO.BATCH    5
etr_sch.2018        JU.BATCH    2
etr_sch.2018        KP200.BATCH	1
etr_sch.2018        LP.BATCH    2
etr_sch.2018        MC.BATCH    2


select  servername
    ,   sum(case when batch_id = 'JU.BATCH' then cnt end) ju_batch
    ,   sum(case when batch_id = 'FO.BATCH' then cnt end) fo_batch
    ,   sum(case when batch_id = 'KP200.BATCH' then cnt end) kp200_batch
    ,   sum(case when batch_id = 'LP.BATCH' then cnt end) lp_batch
    ,   sum(case when batch_id = 'MC.BATCH' then cnt end) mc_batch
from
(
    select  servername, batch_id, count(*) cnt 
    from    f_pgm_info
    where   biz_dt between cast(convert(varchar,getdate(),111)+' 00:00:00:000' as datetime)
                       and cast(convert(varchar,getdate(),111)+' 23:59:59:998' as datetime)
    and     batch_id like '%.BATCH'
    group by servername, batch_id
) a 
group by servername
order by servername
;

ebroker.2035        2   5   1   2   2
ebroker_save.2076   2   5   1   2   2
etr.2034            2   5   1   2   2
etr_ord.2019        2   5   1   2   2
etr_sch.2018        2   5   1   2   2


출처 : userpark.net 


MS SQL Server 백업 / 복구 시나리오  

I. 백업의 종류  

1. 전체 백업(full backup)  
: 데이터 전체를 백업한다. 또한 진행 중인 트랜잭션의 로그도 받는다.  
(로그 전체를 백업 받는 것은 아니다.)  

2. 차등 백업(differential backup)  
: 마지막 전체 백업 이후 변경된 모든 데이터 페이지를 백업한다.  
따라서 전체 백업을 받은 후 차등 백업을 두 번 받았다면 두번째 차등백업은 첫번째  
차등백업의 내용도 포함하고 있다.  
백업 시간은 오래 걸리지만 복원 속도가 빠르다는 장점이 있다.  

3. 트랜잭션 로그 백업(transaction log backup)  
: 일종의 incremental 백업으로, 로그 백업을 받으면 백업 받은 로그는 지워지므로  
동일한 내용이 다시 백업 되지 않는다. 따라서 로그 백업은 전에 받은 로그 백업  
이후의 것만 백업이 된다. 백업은 빠르지만 복원은 전체백업을 복구한 후 각각의  
로그 백업을 복구해야 하므로 시간이 오래 걸린다.  
또한 만약 중간의 로그 백업을 잃어 버리면 그 전의 로그 백업까지의 데이터만 살릴  
수 있다.  


II. 기본적인 복구 시나리오.  
: 다음의 3가지로 나눠볼 수 있다.  
- 전체백업에서 복구  
- differential 백업에서 복구  
- transaction log 백업에서 복구  

1. 전체 백업에서 복원하기  
: 전체 백업에서 복원은 항상 마지막 전체 백업으로부터 복원을 한다.  
다음 작업은 백업을 위한 디바이스를 만들고 데이터를 변경한 후,  
전체백업을 받고 이 백업을 이용해 복원을 하는 과정이다.  

1) 백업 디바이스 만들기  
sp_addumpdevice 'disk','pubs_bak','c:pubsbk.bak'  
(C:에 pubsbk.bak라는 백업 디바이스를 만들고 이름을 pubs_bak라 한다.)  

2) pubs database를 변경한 후 변경을 확인한다.  
create table test1 (id int, name char(10))  
insert test1 values (1, '사용자1')  
select * from test1 (“1, 사용자1”이 나타난다.)  

3) pubs database를 백업 받는다.  
backup database pubs to pubs_bak  

4) 문제를 발생시키고 난 후(pubs를 지운 후), 기존의 백업으로부터 복원을 한다.  
use master  
drop database pubs  
go  
restore database pubs from pubs_bak  

5) 복구된 데이터를 확인한다.  
use pubs  
select * from test1  

2. Differential 백업에서 복구하기  
: differential 백업의 특징은 매번 백업을 받을 때마다 이전 전체백업 이후의 모든  
데이터를 다시 백업 받는다는 것이다. 따라서 모든 데이터가 백업을 받을 때마다  
중복되어 받으므로 백업 시간이 오래 걸린다. 하지만 복원 시에는 전체 백업과  
마지막에 받은 differential 백업만 있으면 되므로 복원속도가 상당히 빠르다.  

다음 작업은 전체 백업을 받은 후 두 번의 differential 백업을 받은 후 복원 시에는  
전체 백업과 두번째의 differential 백업을 가지고 복원하는 과정을 나타낸 것이다.  

또한 특기할 만한 사항은 데이터가 손상되어 database에 접근을 할 수 없는 경우에도  
그때까지의 로그를 받을 수 있다는 것이다.  

backup log … with no_truncate 옵션을 사용하면 된다.  

1) 백업 디바이스를 만든다.  
exec sp_addumpdevice 'disk', 'pubs_full', 'c:pubs_full.bak'  
exec sp_addumpdevice 'disk', 'pubs_diff1', 'c:pubs_diff1.bak'  
exec sp_addumpdevice 'disk', 'pubs_diff2', 'c:pubs_diff2.bak'  
exec sp_addumpdevice 'disk', 'pubs_log', 'c:pubs_log.bak'  
(전체백업용, 각각의 differential 백업용, 로그 백업용)  

2) pubs database를 완전복구 모드로 변경한다.  
alter database pubs set recovery full  
exec sp_helpdb pubs  

3) db를 변경한다.  
use pubs  
go  
create table test_diff (name char(10), score int)  
insert test_diff values('학생1', 100)  
go  
select * from test_diff (=> “학생1, 100”이 출력된다.)  

4) pubs database를 full backup한다.  
backup database pubs to pubs_full  

5) db를 변경한다.  
insert test_diff values('학생2', 900)  
select * from test_diff (=> “학생1, 100”, “학생2, 90”이 출력된다.)  

6) 첫번째 differential 백업을 한다.  
backup database pubs to pubs_diff1 with differential  

7) db를 다시 변경한다.  
insert test_diff values('학생3', 80)  
select * from test_diff  
(=> “학생1, 100”, “학생2, 90”, “학생3, 80”이 출력된다.)  

두번째 differential 백업을 받는다.  
backup database pubs to pubs_diff2 with differential  

9) db를 변경한다.  
insert test_diff values ('학생4', 60)  
select * from test_diff  
(=> “학생1, 100”, “학생2, 90”, “학생3, 80”, “학생4, 60”가 출력된다.)  

10) db에 문제를 발생시킨다.  
SQL Server 서비스를 정지한 후, pubs database의 data file(pubs.mdf)을  
지운다. 다시 SQL Server 서비스를 시작하면, pubs database가 suspect상태가  
된다.  

11) pubs database에 대한 log를 백업 받는다.  
비록 pubs database는 문제가 발생하여 접근할 수 없지만, log는 받을 수 있다.  
backup log pubs to pubs_log with no_truncate  

12) pubs database를 복원한다.  
이때 필요한 백업은 다음과 같다.  

a. 전체 백업.  
b. 두번째 differential 백업.  
c.마지막에 받은 로그 백업.  
use master  
go  
restore database pubs from pubs_full with norecovery  
restore database pubs from pubs_diff2 with norecovery  
restore log pubs from pubs_log  
(이 때 마지막 백업을 복원하는 것 외에는 반드시 with norecovery 옵션을  
붙여줘야 한다. norecovery옵션은 다음에 더 복원될 부분이 남아있다는 뜻.)  

13) select를 이용하여 데이터가 다 복구되었는지 알아본다.  
use pubs  
go  
select * from test_diff (=> differential 백업 이후에 추가된 사항에 대해서도  
완벽하게 복구되는 것을 알 수 있다.)  

3. Transactional Log 백업에서 복구하기  
: transactional log 백업은 전체 백업 후 변경된 부분을 백업을 받는다. 백업 후에는  
로그를 지워버리므로 로그백업을 받으면 이전 백업 받은 다음 부분부터 백업을  
받는다. 이 방법은 백업시간은 단축되지만 복원은 전체 백업과 모든 로그 백업이  
있어야 하므로 시간이 많이 걸린다. 또한 중간의 로그를 잃어 버리면 그 다음 로그는  
백업에 사용될 수 없다.  

이 작업에서는 전체백업을 받은 후 로그 백업을 여러 번 받고 복원 시 전체 백업과  
각각의 로그 백업을 이용하는 것을 보여준다.  

1) 백업에 사용될 디바이스를 만든다.  
(전체백업, 각각의 로그 백업을 위한 디바이스를 만든다.)  

use master  
go  
exec sp_addumpdevice 'disk', 'nwind_full', 'c:nwindfull.bak'  
exec sp_addumpdevice 'disk', 'nwind_log1', 'c:nwindlog1.bak'  
exec sp_addumpdevice 'disk', 'nwind_log2', 'c:nwindlog2.bak'  
exec sp_addumpdevice 'disk', 'nwind_log', 'c:nwindlog.bak'  

2) northwind database를 완전복구 모드로 바꾼다.  
alter database northwind set recovery full  
exec sp_helpdb northwind  

3) database에 새로운 테이블을 만들고 데이터를 입력한다.  
use northwind  
go  
create table nwind_log (id int, name char(10))  
go  
select * from nwind_log  
go  

insert nwind_log values(1, '손님1')  
select * from nwind_log  

4) 전체 백업을 받는다.  
backup database northwind to nwind_full  
go  

5) 데이터를 추가한다.  
insert nwind_log values(2, '손님2')  
select * from nwind_log  

6) 첫번째 로그 백업을 받는다.  
backup log northwind to nwind_log1  

7) 데이터를 추가한다.  
insert nwind_log values(3, '손님3')  
select * from nwind_log  

두번째 로그 백업을 받는다.  
backup log northwind to nwind_log2  

9) 데이터를 추가한다.  
insert nwind_log values (4, '손님4')  
select * from nwind_log  

10) 장애를 발생시킨다.  
(SQL Server 서비스를 멈춘 후 northwnd.mdf 파일을 삭제하고 다시 SQL Server  
서비스를 시작한다. 그러면 northwind database가 suspect 상태로 된다.)  

11) 장애가 발생한 시점까지의 로그를 백업 받는다.  
backup log northwind to nwind_log with no_truncate  

12) 복원을 하는데 이번에는 전체 백업과 모든 로그 백업이 필요하다.  
use master  
go  
restore database northwind from nwind_full with norecovery  
restore log northwind from nwind_log1 with norecovery  
restore log northwind from nwind_log2 with norecovery  
restore log northwind from nwind_log  

13) 데이터를 다시 select하여 모든 데이터가 들어있는 것을 확인한다.  
use northwind  
go  
select * from nwind_log  

III. 고급 복구 시나리오  
- 파일그룹 백업 및 복구  
- 특정 시점으로 복원하기(stopat, stopatmark등)  
- 데이터 파일로부터 복원(sp_attach_db, sp_attach_single_file_db등)  

1. 파일 그룹 백업 및 복구  
: 파일 그룹은 대용량 DB를 유지관리하기 쉽게 하기 위해 생겨난 개념으로 데이터를  
각각 다른 하드에 분산 저장하며 백업과 복원을 각 파일 그룹별로 할 수 있어 전체  
데이터베이스를 백업할 때에 비해 월등한 속도향상과 편의성을 제공한다.  

이 작업에서는 파일 그룹을 생성한 후 각 파일 그룹별로 백업을 받고 그 중 하나의  
데이터 파일이 손상되었을 때 복원하는 방법을 알아 본다.  

1) 파일 그룹 생성.  
CREATE DATABASE fileG ON (NAME = fileG, FILENAME = 'c:datafileG.mdf'  
, SIZE = 10),  
FILEGROUP fileG2 (NAME = fileG2, FILENAME = 'd:datafileG2.ndf'  
, SIZE = 10),  
FILEGROUP fileG3 (NAME = fileG3, FILENAME = 'e:datafileG3.ndf'  
, SIZE = 10)  
LOG ON (NAME = 'fileGLog', FILENAME = 'f:datafileG.ldf', SIZE = 5MB)  
GO  

: fileG database의 data부분을 C:, D:, E:에 나누어서 생성을 하였고,  
로그도 F: 드라이브에 별도로 생성을 하였음.  

2) 데이터를 추가한다.  
use fileG  

create table a(id int)  
create table b(id int)on fileG2  
create table c(id int)on fileG3  
EXEC sp_helpdb fileG  

3) 전체 백업을 받는다.  
backup database fileG to disk='c:full.bak'  

4) primary file Group에 데이터를 집어 넣는다.  
insert a values(1)  

5) primary file Group만 백업을 받는다.  
backup database fileG filegroup='primary'  
to disk='c:file1.bak'  

6) 데이터를 fileG2와 fileG3에도 넣는다.  
insert b values(1)  
insert c values(1)  

7) fileG에 대해 로그 백업을 받는다.  
backup log fileG to disk='c:log1.bak'  

두번째 파일 그룹인 fileG2에 대한 데이터 백업을 받는다.  
backup database fileG filegroup='fileG2' to disk='c:file2.bak'  

9) 데이터를 추가한다.  
insert b values(2)  
insert c values(2)  
insert a values(2)  

10) fileG에 대한 로그 백업을 받는다.  
backup log fileG to disk='c:log2.bak'  

11) 세번째 파일 그룹인 fileG3에 대해 백업을 받는다.  
backup database fileG filegroup='fileG3'  
to disk='c:file3.bak'  

12) 데이터를 업데이트 한다.  
insert c values(3)  
insert a values(3)  
insert b values(3)  

13) 이 때 장애가 세번째 파일 그룹의 데이터 파일이 손상되었다.  
(fileG3.ndf를 삭제한다.)  

14) 이 때까지의 로그를 받는다.  
backup log fileG to disk='c:log3.bak'  
with init,no_truncate  

15) 이제 손상된 fileG를 복원한다.  
이때는 전체 백업을 복구하는 것이 아니라 세번째 파일 그룹에 대한 백업과  
마지막에 받은 로그를 가지고 복원을 한다.  

restore database fileG filegroup='fileG3'  
from disk='c:file3.bak'with norecovery  

restore log fileG from disk='c:log3.bak'  

16) fileG의 모든 table을 select 해 본다.  
use fileG  
go  
select * from a  
select * from b  
select * from c  

모든 항목들이 다 나와 있음을 알 수 있다.  
File Group의 특징은 대용량 데이터 베이스를 백업 및 복원을 할 때 모든  
데이터에 대하여 하는 것이 아니라 file Group 별로 나누어 백업을 하고 특정  
파일그룹이 깨진 경우에는 해당되는 file Group의 백업만 복원해 주면 되므로  
백업과 복원에 시간이 적게 걸리게 되어 유지보수에 유용한 모델이다.  

2. 특정 시점으로 복원하기.  
: stopat, stopatmark, stopbeforemark  

1) stopat  
: 데이터를 원하는 시점으로 되돌릴 수 있는 방법.  
다음과 같은 경우에 적용된다.  
a. 오전 9:00 전체백업  
b. 다양한 작업을 함.  
c. 오전 10:00 실수로 where 절 없이 delete문 수행하여 테이블 A의 모든 데이터를  
지움.  
d. 오전 10:30분 로그 백업.  
e. 11:00 대량으로 잘못된 작업이 수행되었음을 발견.  

이 때는 다음과 같이 작업해 준다.  
ㄱ. 전체백업을 복원한다.  
ㄴ. 백업된 로그를 복원하면서 다음과 같은 옵션을 준다.  
RESTORE LOG … FROM …  
WITH STOPAT = ‘2001-12-24 10:00:01  

예제] pubs db를 이용하여 stopat을 테스트 해 본다.  
a. 현재의 titles.price 컬럼의 가격이 얼마로 시작되는지 확인한다.  
SELECT TOP 1 price FROM pubs..titles (19.99가 나온다.)  
b. pubs database를 전체복구 모드로 바꾼다.  
ALTER DATABASE pubs SET RECOVERY full  
Exec sp_helpdb pubs  
c. pubs database를 전체백업 받는다.  
BACKUP DATABASE pubs to disk = ‘c:pubsfull.bak’  
d. titles의 값을 두배로 만든다.  
UPDATE pubs..titles SET price=price*2  
SELECT TOP 1 price FROM pubs..titles (39.98이 나온다.)  
e. log 백업을 받는다.  
BACKUP LOG pubs to disk = ‘c:pubslog1.bak’  
f. 한번 더 가격을 인상한다.  
UPDATE pubs..titles SET price=price*2  
SELECT TOP 1 price FROM pubs..titles (79.96이 나온다.)  
g. 서버의 시간을 기록한다.  
SELECT getdate()  
h. 이번에는 titles의 값을 전부 100으로 변경한다.  
(이 작업은 잠시 시차를 두고 하는 것이 좋다.)  
UPDATE pubs..titles SET price = 100  
SELECT TOP 1 price FROM pubs..titles (100이 나온다.)  
i. 다시 한번 로그 백업을 받는다.  
BACKUP LOG pubs to disk = ‘c:pubslog2.bak’  
j. 지금 현재 full 백업과 로그 백업이 2개가 있다.  
각각을 사용하여 복원을 시도해 보면 다음과 같은 결과가 나온다.  

Full 백업만 사용하여 복원한 경우  
RESTORE DATABASE pubs FROM disk = ‘c:pubsfull.bak’  
SELECT TOP 1 price FROM pubs..titles (19.99가 나온다.)  

첫번째 로그까지 사용하여 복원한 경우  
RESTORE DATABASE pubs FROM disk = ‘c:pubsfull.bak  
WITH NORECOVERY  
RESTORE LOG pubs FROM disk = ‘c:pubslog1.bak’  
SELECT TOP 1 price FROM pubs..titles (39.98이 나온다.)  

두번째 로그까지 사용하여 복원한 경우  
RESTORE DATABASE pubs FROM disk = ‘c:pubsfull.bak’  
WITH NORECOVERY  
RESTORE LOG pubs FROM disk = ‘c:pubslog1.bak’  
WITH NORECOVERY  
RESTORE LOG pubs FROM disk = ‘c:pubslog2.bak’  
SELECT TOP 1 price FROM pubs..titles (100이 나온다.)  
잘못된 작업을 하기 전의 데이터로 돌리려면 STOPAT 옵션을 사용한다.  

RESTORE DATABASE pubs FROM disk =’c:pubsfull.bak’  
WITH NO RECOVERY  
RESTORE LOG pubs FROM disk = ‘c:pubslog1.bak’  
WITH NORECOVERY  
RESTORE LOG pubs FROM disk = ‘c:pubslog2.bak’  
WITH STOPAT = ‘2001-12-24 14:53:07.310’(잘못된 작업을 하기 전의 시간)  

SELECT TOP 1 price FROM pubs..titles  
(79.96이 나온다. 즉, 잘못된 작업을 하기 전의 값이 나온다.)  

일반적으로 STOPAT 옵션은 시간을 기억해서 올바른 시간을 입력해야 한다는 부담이  
있다. 이것을 극복하기 위해 나온 옵션이 STOPATMARK 옵션이다.  

2) STOPATMARK, STOPBEFOREMARK  
: STOPAT옵션이 시간을 기반으로 되돌리는 작업을 했다면, STOPATMARK는 해당 MARK까지  
복원한다. 이를 사용하기 위해서는 트랜잭션을 시작할 때 마크를 지정해야 한다.  

BEGIN TRAN tran1 WITH MARK  
… (해당작업)  
COMMIT  

복원 시는 다음과 같이 사용한다.  

RESTORE LOG … FROM …  
WITH STOPATMARK = ‘tran1’  

Transaction이 여러 번 사용되었으면 다음과 같이 AFTER 옵션을 준다.  
WITH STOPAT = ‘tran1’ AFTER 2001-12-24 14:53:07.310  
STOPATMARK는 해당 트랜잭션까지 복원하고,  
STOPBEFOREMARK는 해당 트랜잭션 직전에서 복원을 중지한다.  

예제] stopatmark를 이용하여 데이터 복구하기  
a. pubs database를 Full 복원모드로 변경한 후 전체 백업을 받는다.  
ALTER DATABASE pubs SET RECOVERY FULL  
BACKUP DATABASE pubs to disk = ‘c:fullpubs.bak’  
b. sales table에 총 21건의 데이터가 있음을 확인하다.  
use pubs  
select count(*) from sales  
c. 다음과 같은 트랜잭션에 표시를 주고 시작한다.  
Begin tran tran1 WITH MARK  
set rowcount 10  
delete sales  
set rowcount 0  
select count(*) from sales  
COMMIT (10개의 행이 지워져 총 11행이 남는다.)  
d. 다시 모든 행을 지운다.  
delete sales  
e. 로그 백업을 한다.  
BACKUP LOG pubs to disk = ‘c:log1.bak’  
f. 백업을 모두 복원한다.  
use master  
RESTORE DATABASE pubs FROM disk = ‘c:fullpubs.bak’  
WITH NORECOVERY  
RESTORE LOG pubs FROM disk = ‘c:log1.bak’  
SELECT count(*) from pubs..sales  
( 0개가 나온다.)  
g. STOPATMARK를 사용하여 transaction의 끝까지만 복원을 한다.  
RESTORE DATABASE pubs FROM disk = ‘c:fullpubs.bak’  
WITH NORECOVERY  
RESTORE LOG pubs FROM disk = ‘c:log1.bak’  
WITH STOPATMARK = ‘tran1’  
SELECT count(*) from pubs..sales  
(11건의 데어터가 나온다.)  

h. STOPBEFOREMARK를 사용하여 표시된 트랜잭션 작업 직전까지만 복원한다.  
RESTORE DATABASE pubs FROM disk = ‘c:fullpubs.bak’  
WITH NORECOVERY  
RESTORE LOG pubs FROM disk = ‘c:log1.bak’  
WITH STOPBEFOREMARK = ‘tran1’  

SELECT count(*) from pubs..sales  
(21건이 나온다.)  

3) 데이터 파일로부터 복원  
: sp_attach_db, sp_detach_db, sp_attach_single_file_db  
DB를 복구하려고 하는데 백업본이 없고 데이터 파일과 로그 파일만 있는 경우 또는  
데이터 파일만 있는 경우 이 파일들을 이용하여 데이터베이스를 복원할 수 있다.  

a. sp_attach_db  
: 데이터와 로그 파일을 모두 가지고 있는 경우 사용되는 방법으로 사용법은  
다음과 같다.  

sp_attach_db ‘test’, ‘c:datatest.mdf’,  
‘c:datatest_log.ldf’  
(test.mdf와 test_log.ldf 두 개의 파일이 있을 때 이 파일을 이용하여  
test라는 database를 만든다.)  

b. sp_attach_single_file_db  
: 로그 파일은 없고 데이터 파일만 남은 경우 사용한다.  

sp_attach_single_file_db ‘test’, ‘c:datatest.mdf’  
(test.mdf 데이터 파일만 가지고 test db를 다시 만들었다.  
이 때 로그는 새로 만들어 진다.)  

c. sp_detach_db  
: 기존의 database를 data와 log 파일만 남겨 놓은 채 지워버린다.  

sp_detach_db ‘test’  
(test database를 지운 후 데이터 파일과 로그 파일만 남긴다.  
이 프로시저는 sp_attach_db와 같이 사용되어 한 서버에  
있는 데이터베이스를 다른 서버로 이동할 때 사용된다.)  

예제] sp_detach_db와 sp_attach_db, sp_attach_single_file_db 를 이용하기  

a. pubs database를 detach 한다.  
sp_detach_db pubs  

b. select를 수행하여 pubs database의 테이블을 query한다.  
SELECT * from pubs..titles  
(pubs database를 찾을 수 없다고 나온다.)  

c. pubs.mdf와 pubs_log.ldf를 c:data 폴더로 옮긴다.  

d. pubs database를 attach한다.  
sp_attach_db 'pubs', 'c:datapubs.mdf',  
'c:datapubs_log.ldf'  

e. SELECT를 하여 pubs database가 다시 사용가능한 것을 확인한다.  
SELECT * from pubs..titles  
(결과값이 출력되는 것을 확인할 수 있다.)  

f. 다시 pubs database를 detach한다.  
sp_detach_db pubs  

g. 로그파일(pubs_log.ldf)을 삭제한다.  

h. sp_attach_single_file_db를 실행한다.  
sp_attach_single_file_db ‘pubs’, ‘c:datapubs.mdf’  
(pubs database가 다시 생기고 log도 다시 생성되어 있음을 알 수 있다.)
 

'MSSQL' 카테고리의 다른 글

(펌) T_SQL 같은 그룹의 데이터를 붙여서 출력하기  (0) 2011.12.27
T-SQL) group by 결과를 가로로 출력  (0) 2011.12.27
(펌) MSSQL 2005 백업계획작성  (0) 2011.10.20
(펌) MSSQL lock & blocking  (0) 2010.12.07
(펌) MSSQL Lock  (0) 2010.12.07


출처 : http://www.linxus.co.kr/post/43376

exec sp_lock

잠금 관련 정보를 보고합니다.

열 이름 데이터 형식 설명
spid smallint SQL Server 프로세스 ID 번호입니다.
dbid smallint 잠금을 요청하는 데이터베이스 ID 번호입니다.
ObjId int 잠금을 요청하는 개체의 개체 ID 번호입니다.
IndId smallint 인덱스 ID번호입니다.
Type nchar(4) 잠금 유형입니다.

DB = 데이터베이스
FIL = 파일
IDX = 인덱스
PG = 페이지
KEY = 키
TAB = 테이블
EXT = 익스텐트
RID = 행 식별자

Resource nchar(16) syslockinfo.restext의 값에 해당되는 잠금 리소스입니다.
Mode nvarchar(8) 잠금 요청자의 잠금 모드입니다. 이 잠금 모드는 허가된 모드, 변환 모드 또는 대기 모드를 표시합니다.
Status int 잠금 요청 상태입니다.

허가
대기
변환


exec sp_who {sp_id}

현재 Microsoft? SQL Server™ 사용자 및 프로세스에 관한 정보를 제공합니다. 유휴 상태가 아닌 프로세스만 반환하려면 반환되는 정보를 필터링하면 됩니다.

데이터 형식 설명
spid smallint 시스템 프로세스 ID입니다.
ecid smallint 특정 SPID와 관련된 주어진 스레드의 실행 컨텍스트 ID입니다.

ECID = {0, 1, 2, 3, ...n}이며, 0은 항상 주 또는 부모 스레드를 의미하며 {1, 2, 3, ...n}는 하위 스레드를 의미합니다.

status nchar(30) 프로세스 상태입니다.
loginame nchar(128) 특정 프로세스와 관련된 로그인 이름입니다.
hostname nchar(128) 각 프로세스의 호스트 또는 컴퓨터 이름입니다.
blk char(5) 프로세스를 차단하는 데 필요한 시스템 프로세스 ID입니다. 단, 존재하는 경우에 한합니다. 없는 경우, 이 열은 0이 됩니다.

고아 분산 트랜잭션이 주어진 spid 와 관련된 트랜잭션을 차단하면 이 열은 고아 트랜잭션을 차단하기 위해 '-2'를 반환합니다.

dbname nchar(128) 프로세스가 사용하는 데이터베이스입니다.
cmd nchar(16) Transact-SQL 문, SQL Server 내부 엔진 프로세스 등, 프로세스 실행에 필요한 SQL Server 명령입니다.


DBCC INPUTBUFFER( sp_id )

클라이언트에서 Microsoft? SQL Server™에 최종적으로 보낸 명령문을 표시합니다.

DBCC INPUTBUFFER는 다음과 같은 열이 있는 행 집합을 반환합니다.

열 이름 데이터 형식 설명
EventType nvarchar(30) 이벤트 유형(예: RPC, Language, NO Event)
Parameters Int 0 = 텍스트
1- n = 매개 변수
EventInfo nvarchar(255) RPC EventType의 경우, EventInfo에 프로시저 이름만 포함됩니다. Language 또는 No Event EventType의 경우, 이벤트의 첫 255자만 표시됩니다.

 


 

다음 내용은 MSDN에서 블록킹 해결하는 방법입니다.
 
블로킹은 잠금(Lock)을 기반으로 한 동시성을 지원하는 관계형 데이터베이스 관리 시스템(RDBMS)의 피할 수 없는 특성입니다. SQL Server에서 블로킹은 한 SPID가 특정 리소스에 대해 잠금(Lock)을 걸었는데 둘째 SPID가 동일한 리소스에 대해 충돌하는 잠금(Lock) 유형을 확보하려고 할 때 발생합니다. 일반적으로, 첫째 SPID가 리소스를 잠그는 시간 프레임은 매우 작습니다. 잠금(Lock)을 풀면 두번 째 연결은 자유롭게 리소스에서 자체 잠금(Lock)을 걸고 계속 처리할 수 있습니다. 이는 정상적인 동작이며 하루종일 시스템 성능에 인식할 수 있을만한 영향 없이 여러 번 일어날 수 있습니다.

쿼리의 기간 및 트랜잭션 컨텍스트는 잠금(Lock)이 걸리는 시간을 나타내고 그 결과 다른 쿼리에 영향을 줄 수 있습니다. 쿼리가 트랜잭션에서 실행되지 않으면(그리고 잠금 참고(Lock HInts)가 사용되지 않으면) SELECT 문에 대한 잠금(Lock)은 쿼리 시간 대신 실제로 읽힐 때 리소스에만 걸립니다. INSERT, UPDATE 및 DELETE 문에 대해 잠금(Lock)은 데이터 일관성 및 필요한 경우 쿼리 롤백을 위해 쿼리의 시간 동안 걸립니다.

트랜잭션에서 실행되는 쿼리에 대해 잠금(Lock)이 걸린 시간은 쿼리 유형, 트랜잭션 격리 수준(Isolation Level) 그리고 쿼리에서의 잠금 참고(Lock Hints) 사용 여부에 의해 결정됩니다. 잠금(Lock), 잠금 참고(Lock Hints) 참고 및 트랜잭션 격리 수준(Isolation Level)에 대한 설명은 SQL Server 온라인 설명서에 있는 다음 항목을 참조하십시오.
"Understanding Locking in SQL Server"
"Locking Architecture"
"Lock Compatibility"
"Locking Hints"
"Changing Default Locking Behavior in Oracle and SQL Server"
잠금(Lock) 및 블로킹이 시스템 성능을 저하시키는 지점까지 증가하는 경우 이는 일반적으로 다음과 같은 이유 중 하나에 의해서 입니다.

SPID는 잠금(Lock)을 풀기 전에 확장된 시간 동안 리소스 집합에 대해 잠금(Lock)을 겁니다. 이러한 블로킹 유형은 시간이 지나면서 스스로 해결될 수 있지만 성능이 저하될 수 있습니다.
SPID는 리소스 집합에 대해 잠금(Lock)을 걸고 이를 절대로 풀지 않습니다. 이러한 블로킹 유형은 자체적으로 해결되지 않으며 영향을 받은 리소스에 무한정으로 액세스하지 못하게 합니다.
위의 첫 시나리오에서 블로킹 문제는 SPID가 잠금(Lock)을 풀면 자체적으로 해결됩니다. 그러나 서로 다른 SPID는 시간이 지남에 따라 이동 대상을 만들면서 여러 리소스를 블로킹할 수 있으므로 상황은 매우 달라집니다. 이러한 이유로 SQL Server Enterprise Manager(엔터프라이즈 관리자) 또는 개별 SQL 쿼리를 사용하여 상황을 해결하기 힘듭니다. 두 번째 상황의 결과로 진단하기 쉬운 일관성 있는 상태가 나옵니다.

블로킹 정보 수집

블로킹 문제 해결의 어려움을 해결하기 위해 데이터베이스 관리자는 지속적으로 SQL Server에서의 잠금(Lock) 및 블로킹 상태를 모니터하는 SQL 스크립트를 사용할 수 있습니다. 이러한 스크립트는 시간에 따른 특정 인스턴스의 스냅샷을 제공함으로 문제의 전체적인 그림을 만들어냅니다. SQL 스크립트로 블로킹을 모니터하는 방법에 대한 설명은 Microsoft 기술 자료에 있는 다음 문서를 참조하십시오. 본 문서에 있는 스크립트는 아래의 작업을 수행합니다. 가능하면 Enterprise Manager(엔터프라이즈 관리자) 또는 특정 SQL 쿼리에서 이 정보를 얻는 방법이 제공됩니다.
1. 블로킹 체인 헤드에서 SPID를 식별합니다.
위의 문서에 나온 스크립트를 사용하는 것 외에 다음과 같이 SQL Enterprise Manager(엔터프라이즈 관리자)를 사용하여 블로킹 체인의 헤드를 식별할 수도 있습니다.

a. 서버 그룹을 확장한 다음 서버를 확장합니다.
b. Management를 확장한 다음 Current Activity를 확장합니다.
c. Locks / Process ID를 확장합니다. 블로킹 정보와 함께 SPID는 세부 정보 창에 표시됩니다. 다른 것을 블로킹하는 SPID는 "(Blocking)"으로 나타납니다.
그러나 일부 tempdb 블로킹 문제는 사용자가 임시 테이블 작업을 사용하는 쿼리를 실행하지 못하도록 할 수 있으므로 때때로 Enterprise Manager(엔터프라이즈 관리자) 대신 쿼리를 사용해야 할 때가 있습니다. 직접 쿼리를 사용하면 이 문제를 피하는데 필요한 컨트롤을 제공합니다.
2. 블로킹 SPID가 실행 중인 쿼리를 찾습니다.
스크립트 메서드는 다음 쿼리를 사용하여 특정 SPID에서 실행된 명령을 결정합니다.

DBCC INPUTBUFFER (<spid>)
또는 다음과 같이 SQL Enterprise Manager(엔터프라이즈 관리자)를 사용할 수 있습니다.

a. 서버 그룹을 확장한 다음 서버를 확장합니다.
b. Management를 확장한 다음 Current Activity를 확장합니다.
c. Process Info를 누릅니다. SPID는 세부 사항 창에 표시됩니다.
d. 블로킹 SPID를 두 번 눌러 SPID가 실행한 최근 Transact-SQL 명령 일괄 처리를 봅니다.
3. 블로킹 SPID가 걸고 있는 중인 블로킹 유형을 찾습니다.
sp_loc 시스템 저장 프로시저(Stored Procedure)를 실행하여 이 정보를 결정할 수 있습니다. 또는 다음과 같이 Enterprise Manager(엔터프라이즈 관리자)를 사용할 수 있습니다.

a. 서버 그룹을 확장한 다음 서버를 확장합니다.
b. Management를 확장한 다음 Current Activity를 확장합니다.
c. Locks / Process ID를 확장합니다. 걸고 있는 잠금(Lock)에 대한 정보와 함께 SPID는 세부 사항 창에 표시됩니다.
4. 블로킹 SPID의 트랜잭션 중첩 수준(Nesting Level) 및 프로세스 상태를 찾습니다.
SPID의 트랜잭션 중첩 수준(Nesting Level)은 @@TRANCOUNT 전역 변수에서 사용할 수 있습니다. 그러나 다음과 같이 sysprocesses 테이블을 쿼리하여 SPID 외부에서 결정될 수 있습니다.

SELECT open_tran FROM SYSPROCESSES WHERE SPID=<blocking SPID number>
go
반환된 값은 SPID의 @@TRANCOUNT 값입니다. 이는 블로킹 SPID의 트랜잭션 중첩 수준(Nesting Level)을 보여 주는데, 이는 잠금(Lock)을 건 이유를 설명할 수 있습니다. 예를 들어, 값이 0보다 크면 SPID는 트랜잭션 중간에 있습니다(트랜잭션 격리 수준에 따라 확보한 특정 잠금(Lock)을 걸 것으로 기대하는 경우).

DBCC OPENTRAN 데이터베이스_이름을 사용하여 데이터베이스에 장기 개방 트랜잭션이 있는지 확인할 수도 있습니다.

SQL Server Profiler(프로필러) 추적(Trace) 정보 수집

위의 정보 외에 서버에서 이루어진 작업의 Profiler(프로필러) 추적(Trace)을 캡처하여 SQL Server에서의 블로킹 문제를 철저히 확인해야 합니다. SPID가 한 트랜잭션에서 여러 문을 실행하면 마지막 문만 DBCC INPUTBUFFER 출력에 나타납니다. 그러나 이전 명령 중 하나가 아직도 잠금(Lock)이 걸려 있는 원인이 될 수 있습니다. Profiler(프로필러) 추적(Trace)을 사용하면 현재 트랜잭션에서 SPID에 의해 실행된 모든 명령을 볼 수 있습니다. 다음 단계는 SQL Server Profiler(프로필러)를 설정하여 추적(Trace)을 캡처할 수 있도록 합니다.
1. SQL Server Profiler(프로필러)를 엽니다.
2. Tools 메뉴에서 Options를 누릅니다.
3. All Event ClassesAll Data Columns 옵션을 선택해야 합니다.
4. OK를 누릅니다.
5. File 메뉴에서 New를 가리키고 Trace를 누릅니다.
6. General 탭에서 데이터를 캡처할 추적(Trace) 이름과 파일을 지정합니다.
7. Events 탭에서 다음과 같은 이벤트 유형을 추적(Trace)에 추가합니다.

Error and Warning Exception 이 이벤트는 예외가 발생했음을 나타냅니다.
심각도가 25 이하인 예외는 오류가 SQL Server에서 클라이언트로 반환되었음을 나타냅니다. 심각도가 25인 예외는 내부 SQL Server 예외이며 아래의 설명대로 필터 아웃되어야 합니다.
Misc. Attention 이 이벤트는 주의 신호가 발생하였음을 나타냅니다. 주의 신호의 일반 원인은 클라이언트 취소 또는 쿼리 시간 제한입니다.
Sessions Connect 이 이벤트는 새 연결이 만들어졌음을 나타냅니다.
Sessions Disconnect 이 이벤트는 클라이언트 연결이 끊어졌음을 나타냅니다.
Sessions Existing Connection 이 이벤트는 SQL Profiler(프로필러) 추적(Trace)이 시작되는 시점에서 연결이 이루어졌음을 나타냅니다.
TSQL RPC:Starting 이 이벤트는 원격 프로시저 호출(RPC)의 실행이 시작되었음을 나타냅니다.
TSQL SQL:BatchStarting 이 이벤트는 Transact-SQL 일괄 처리의 실행이 시작되었음을 나타냅니다.
Stored Procedures SP: StmtStarting 이 이벤트는 저장 프로시저(Stored Procedure) 내에 있는 문이 실행을 시작하고 있음을 나타냅니다. 저장 프로시저(Stored Procedure) 이름은 해당 이벤트에 대한 텍스트의 시작 부분에 나타납니나.

이 외에도, 자세한 정보를 위해 다음 이벤트를 포함시킬 수 있습니다. 대량 생산 환경에서 실행하는 경우 위의 이벤트는 블로킹 문제를 해결하기에 충분하므로 이러한 이벤트 중 하나만 사용할 수 있습니다. 아래의 추가 이벤트를 포함하면 문제의 근원을 좀더 쉽게 빨리 결정할 수 있지만 시스템의 로드가 추가되고 추적 출력(Trace Output) 크기가 늘어납니다.

Misc. Execution Plan 이 이벤트는 실행된 Transact-SQL 문의 계획(Plan) 트리를 보여줍니다.
Transactions DTCTransaction 이 이벤트는 두 개 이상의 데이터베이스 또는 서버 간의 MS DTC(Microsoft Distributed Transaction Coordinator) 트랜잭션을 추적합니다.
Transactions SQLTransaction 이 이벤트는 SQL BEGIN, SAVE, COMMIT 및 ROLLBACK TRANSACTION 문을 추적(Trace)합니다.
TSQL RPC:Completed 이 이벤트는 원격 프로시저 호출(RPC)의 실행이 완료되었음을 나타냅니다.
TSQL SQL:BatchCompleted 이 이벤트는 Transact-SQL 일괄 처리의 실행이 완료되었음을 나타냅니다.
Stored Procedures SP: StmtCompleted 이 이벤트는 저장 프로시저(Stored Procedure) 내에 있는 문이 실행을 완료했음을 나타냅니다.
8. Data Columns 탭에 다음 열(Column)이 있는지 확인하십시오. Start Time, End Time, Connection ID, SPID, Event Class, Text, Integer Data, Binary Data, Application Name, NT User Name 및 SQL User Name. 위의 두번째 테이블에서 추가 이벤트를 포함하면 Duration, CPU, Reads 및 Writes 데이터 열(Column) 또한 포함시키십시오.
9. Filters 탭에서 SQL Server 내부 예외를 제외합니다. Trace Event Criteria 상자에서 Severity를 선택하고 Maximum 상자에 24를 입력합니다. 그런 다음 OK를 누릅니다.

SQL Server에서 클라이언트로 보내진 오류를 모니터하는데 대한 자세한 내용은 Microsoft 기술 자료에 있는 다음 문서를 참조하십시오.
199037 INF: Trapping Error Messages Sent to Clients from a SQL Server
Profiler(프로필러) 사용에 대한 내용은 SQL Server 온라인 설명서를 참조하십시오.

일반 블로킹 시나리오 식별 및 해결

위의 정보를 확인하여 대부분의 블로킹 문제의 원인을 파악할 수 있습니다. 본 문서의 나머지는 이 정보를 사용하여 일부 일반 블로킹 시나리오를 식별하고 해결하는 방법에 대한 설명입니다. 이 설명에서는 문서 Q251004(앞에서 참조됨)에 있는 블로킹 스크립트를 사용하여 블로킹 SPID에 대한 정보를 캡처하고 위에서 설명한 이벤트를 가지고 Profiler(프로필러) 추적(Trace)을 만들었다고 가정합니다.

블로킹 스크립트 출력 보기

sysprocesses 출력을 확인하여 블로킹 체인의 헤드를 파악합니다.
블로킹 스크립트에 대해 고속 모드(Fast Mode)를 지정하지 않은 경우에는 스크립트 출력에는 다른 것을 블로킹하는 SPID를 나열하는 제목이 "SPIDs at head of blocking chains"인 부분이 있습니다.

   SPIDs at the head of blocking chains
   spid
   ------
   9
   10
고속 옵션(Fast Option)을 지정하면 계속 sysprocesses 출력을 보고 블로킹 헤드를 파악할 수 있습니다. 다음은 간결한 sysprocesses 출력입니다.

   spid   status                         blocked
    9     sleeping                       0
   10     sleeping                       0
   11     sleeping                       13
   12     sleeping                       10
   13     sleeping                       9
   14     sleeping                       12
이러한 경우 SPID 9와 10의 blocked 열(Column)에 0이 있음을 볼 수 있습니다. 이는 둘 다 블로킹되지 않았음을 의미하지만 기타 SPID에 대해 blocked 열(Column)에 나타납니다. 이는 SPID 9와 10이 별도의 블로킹 체인의 헤드에 있음을 나타냅니다.
블로킹 체인 헤드에 있는 SPID에 대한 정보에 대해 sysprocesses 출력을 확인합니다.
다음 sysprocesses 필드를 평가하는 것은 중요합니다.

Status
이 열(Column)은 특정 SPID의 상태를 볼 수 있게 합니다. 일반적으로, sleeping 상태는 SPID가 실행을 완료하고 응용 프로그램에서 다른 쿼리 또는 일괄 처리를 제출할 때까지 기다리고 있음을 나타냅니다. runnable 상태는 SPID가 현재 쿼리를 처리하고 있음을 나타냅니다. 다음 표는 다양한 상태 값에 대한 간단한 설명을 제공합니다.

Background SPID가 백그라운드 작업을 수행 중입니다.
Sleeping SPID가 현재 실행하고 있지 않습니다. 이는 일반적으로 SPID가 응용 프로그램에서 명령을 기다리고 있음을 나타냅니다.
Runnable SPID가 현재 실행 중입니다.
Dormant SPID가 연결이 재설정된 일시 정지 상태에 있음을 나타냅니다.
Rollback SPID가 트랜잭션 롤백 중입니다.
Defwakeup SPID가 해제되고 있는 프로세스에 있는 리소스를 기다리고 있음을 나타냅니다. waitresource 필드는 문제의 리소스를 나타내야 합니다.
Spinloop 프로세스가 SMP 시스템에서 동시성 제어에 사용되는 spinlock을 확보하려고 하면서 기다리고 있습니다.
Open_tran
이 필드는 SPID의 트랜잭션 중첩 수준(Nesting Level)을 알려줍니다. 이 값이 0보다 크면 SPID는 열린 트랜잭션에 있고 트랜잭션의 문으로 확보한 잠금(Lock)을 걸고 있을 것입니다.
Lastwaittype, waittype 및 waittime
lastwaittype 필드는 SPID의 마지막 또는 현재 대기 유형(waittype)을 알려줍니다. 이 필드는 SQL Server에서 새 필드이며 waittype 필드(예약된 내부 이진 열(Binary Column)임)의 문자열 표현입니다. waittype이 0x0000이면 SPID는 현재 아무것도 기다리고 있지 않으며 lastwaittype 값은 SPID에 있던 마지막 waittype을 나타냅니다. waittype이 0이 아니면 lastwaittype 값은 SPID의 현재 waittype을 나타냅니다.

여러 lastwaittypewaittype 값에 대한 간단한 설명에 대해서는 Microsoft 기술 자료에 있는 다음 문서를 참조하십시오.
244455 INF: Definition of Sysprocesses Waittype and Lastwaittype Fields
waittime 값은 SPID가 진행 중인지를 파악할 때 유용할 수 있습니다. sysprocesses 테이블에 대한 쿼리에서 sysprocesses의 이전 쿼리의 waittime 값보다 작은 값을 waittime 열에 반환하면 이는 이전 잠금(Lock)이 확보되었다가 플렸으며 현재 새 잠금(Lock)을 기다리고 있음을 나타냅니다(waittime은 0이 아님을 가정). 이는 sysprocesses 출력 간에 waitresource를 비교하여 확인할 수 있습니다.
Waitresource
이 필드는 SPID가 기다리고 있는 리소스를 나타냅니다. 다음 표는 일반 waitresource 형식과 그 의미를 나열합니다.

테이블 DatabaseID:ObjectID TAB: 5:261575970
이 경우 데이터베이스 ID 5는 pubs 예제 데이터베이스이고 개체 ID 261575970은 titles테이블입니다.
페이지 DatabaseID:FileID:PageID PAG: 5:1:104
이 경우 데이터베이스 ID 5는 pubs이고 파일 ID 1은 기본 데이터 파일이며 페이지 104는 titles 테이블에 속하는 페이지입니다.
DatabaseID:ObjectID:IndexID(인덱스 키에 대한 해시(Hash) 값) 키: 5:261575970:1 (5d0164fb1eac)
이 경우 데이터베이스 ID 5는 pubs이고 개체 ID 261575970은 titles 테이블이며 인덱스 ID 1은 클러스터된 인덱스이고 해시(Hash) 값은 특정 행의 인덱스 키 값을 나타냅니다.
DatabaseID:FileID:PageID:Slot(row) RID: 5:1:104:3
이 경우 데이터베이스 ID 5는 pubs이고 파일 ID 1은 기본 데이터 파일이며 페이지 104는 titles 테이블에 속하는 페이지이고 슬롯 3은 페이지에서 행의 위치를 나타냅니다.
컴파일 DatabaseID:ObjectID TAB: 5:834102012 [[COMPILE]]
이 경우 데이터베이스 ID 5는 pubs이지만 개체 ID 834102012는 저장 프로시저(Stored Procedure)입니다. 이는 SPID가 저장 프로시저(Stored Procedure)에 대한 계획(Plan)이 컴파일되기를 기다리고 있음을 나타냅니다.
기타 열(Column)
나머지 sysprocesses 열(Column)은 문제의 근본에 대한 통찰력도 제공합니다. 그 유용성은 문제의 상황에 따라 달라집니다. 예를 들어, SPID에서 제출한 마지막 일괄 처리가 (last_batch)일 때 특정 네트워크 라이브러리(net_library)에서 특정 클라이언트(hostname)로부터만 발생하는지 파악할 수 있습니다. 모든 sysprocesses 열에 대한 간단한 설명에 대해서는 SQL Server 7.0 온라인 설명서에 있는 "sysprocesses(T-SQL)" 항목을 참조하십시오.

참고: SUID 열(Column)은 역방향 호환성을 위해서만 포함된 열(Column)에서 파생되므로 블로킹 스크립트 출력에 포함되지 않습니다. 이는 SQL Server에서 내부적으로 사용되지 않으며 이를 쿼리하면 성능이 저하될 수 있으므로(파생되었으므로) 포함되지 않습니다.
DBCC INPUTBUFFER 출력을 확인합니다.

블로킹 체인의 헤드에 있는 SPID에 대해 또는 0이 아닌 waittype을 가지고 블로킹 스크립트는 DBCC INPUTBUFFER를 실행하여 그 SPID에 대한 현재 쿼리를 결정합니다.

   DBCC INPUTBUFFER FOR SPID 9
   EventType      Parameters EventInfo
   -------------- ---------- --------------------------------------------
   Language Event 0          update titles set title = title
대부분의 경우 이 쿼리는 다른 사용자를 블로킹하는 잠금(Lock)을 걸 수 있습니다 . 그러나 트랜잭션에 SPID가 있으면 잠금(Lock)은 현재 쿼리가 아닌 이전에 실행된 쿼리에서 얻을 수 있습니다. 그러므로 inputbuffer만 아니라 SPID에 대해 Profiler(프로필러) 출력도 봐야 합니다.

참고: 블로킹 스크립트는 여러 단계로 구성되므로 SPID는 블로킹 체인의 헤드에 첫 섹션으로 나타날 수 있지만 DBCC INPUTBUFFER 쿼리가 실행될 때 이는 더 이상 블로킹되지 않고 INPUTBUFFER는 캡처되지 않습니다. 이는 그 SPID에 대해 블로킹이 스스로를 해결하고 있음을 나타내는데, 이는 문제가 될 수도 있고 그렇지 않을 수도 있습니다. 이 때 블로킹 스크립트의 고속(Fast) 버전을 사용하여 inputbuffer가 지워지기 전에 이를 캡처하거나(비록 보장할 수는 없어도) 그 시간 프레임에서 Profiler(프로필러) 데이터를 보고 SPID가 실행하는 쿼리를 결정할 수 있습니다.
DBCC PSS 출력을 확인합니다.
블로킹 체인의 헤드에 있는 모든 SPID에 대해 DBCC PSS 명령을 실행합니다. PSS는 프로세스 상태 구조로 특정 SPID의 상태에 대한 정보가 들어 있습니다. 블로킹 시나리오의 가장 큰 관심 필드는 ec_stat 필드입니다. 다음은 ec_stat 필드의 일반 값입니다.

0x2 프로세스가 종료되었습니다. 이는 SPID가 중단되었음을 나타냅니다.
0x40 프로세스는 쿼리 시간 제한 또는 취소에 의해 클라이언트로부터 주의 신호를 받았습니다. 이는 현재 활성 쿼리가 중단되고 있기는 하지만 0x2에서와 같이 전체 SPID는 아님을 나타냅니다.
0x80 프로세스가 여러 데이터베이스 트랜잭션에 관련되어 있습니다.
0x200 프로세스가 서버 간 원격 프로시저 호출(RPC)를 수행하고 있습니다.
0x400 사용자가 NOLOCK 참고(Hint)가 있는 쿼리를 수행하고 있습니다.
0x800 프로세스가 롤백 중이며 교착 상태 대상으로 선택될 수 없습니다.
0x2000 프로세스가 현재 중단되고 있습니다.


참고: 고속 모드(Fast Mode)에서 블로킹 스크립트는 DBCC PSS 명령을 실행하지 않습니다. 이 정보의 대부분은 Profiler(프로필러) 추적(Trace) 데이터를 보고 결정될 수 있습니다. 추적(Trace) 파일에서 ATTENTION 신호와 예외를 볼 수 있습니다. 예를 들어, 프로세스가 교착 상태에 있으면 교착 상태가 발생할 때 클라이언트에 반환되는 오류인 정수 데이터 열(Column) 값이 1205인 예외 이벤트를 볼 수 있습니다. ec_stat 값은 0이 아닐 때 좀더 자세히 살펴 보기 위한 신호로 유용하게 사용됩니다. SPID가 "정상" 상태에 있지 않으면 경고합니다. 위의 표와 Profiler(프로필러) 추적(Trace) 정보에서 이에 대한 원인과 그 진행 방법을 파악할 수 있습니다.

Profiler(프로필러) 데이터 보기

블로킹 문제 해결에서 Profiler(프로필러) 데이터를 효율적으로 보는 것이 매우 중요합니다. 가장 중요한 사실은 캡처한 모든 것을 볼 필요 없이 선택적으로 볼 수 있다는 것입니다. Profiler(프로필러)는 캡처된 데이터를 효과적으로 볼 수 있도록 기능을 제공합니다. Properties 대화 상자(File 메뉴에서 Properties 누름)에서 Profiler(프로필러)를 사용하여 데이터 열(Column) 또는 이벤트를 제거하고 데이터 열(Column)을 그룹화(정렬)하며 필터를 적용하여 표시되는 데이터를 제한할 수 있습니다. 전체 추적(Trace)을 검색하거나 Edit 메뉴에서 Find를 눌러 특정 값에 대해 특정 열(Column)만 검색할 수도 있습니다. Profiler(프로필러) 데이터를 SQL Server 테이블(File 메뉴에서 Save As를 가리킨 다음 Table을 누름)에 저장하고 이에 대해 SQL 쿼리를 실행할 수도 있습니다.

이전에 저장한 추적(Trace) 파일에만 필터링을 수행해야 합니다 . 활성화된 추적(Trace)에서 이러한 단계를 수행하면 추적(Trace)을 시작한 후에 캡처한 데이터는 손실될 우려가 있습니다. 먼저 활성화된 추적(Trace)을 파일 또는 테이블에 File 메뉴에서 Save As를 눌러 저장한 다음 진행하기 전에 이를 File 메뉴에서 Open을 눌러 다시 엽니다. 저장된 추적(Trace) 파일에서 작업할 때는 필터링이 필터된 데이터를 영구적으로 제거하지 않으며 단지 모든 데이터를 표시하지 않을 뿐입니다. 특히 중점을 두는 검색에 필요한 이벤트와 데이터 열(Column)을 추가하거나 삭제할 수 있습니다.

찾아야 할 내용:
현재 트랜잭션에서 블로킹 체인의 헤드에 있는 SPID에서 어떤 명령을 실행했습니까?
블로킹 체인의 헤드에 있는 특정 SPID의 추적(Trace) 데이터를 필터합니다(File 메뉴에서 Properties를 누른 다음 Filters 탭에서 SPID 값을 지정함). 그러면 다른 SPID를 블로킹하기 전에 실행한 명령을 확인할 수 있습니다. 트랜잭션 이벤트를 포함하면 트랜잭션이 언제 시작되었는지를 쉽게 식별할 수 있습니다. 그렇지 않으면 Text 열에서 BEGIN, SAVE, COMMIT 또는 ROLLBACK TRANSACTION 작업을 검색할 수 있습니다. sysprocesses 테이블에서 open_tran 값을 사용하여 모든 트랜잭션 이벤트를 잡아야 합니다. 실행된 명령과 트랜잭션 컨텍스트를 알면 SPID에서 잠금(Lock)을 걸고 있는 이유를 파악할 수 있습니다.

이벤트와 데이터 열(Column)을 제거할 수 있다는 것을 유의하십시오. 시작 및 완료 이벤트를 둘 다 보는 대신 하나를 선택하십시오. 블로킹 SPID가 저장 프로시저(Stored Procedure)가 아니면 SP:Starting 또는 SP:Completed 이벤트를 제거합니다. SQLBatchRPC 이벤트는 프로시저 호출을 보여 줍니다. SP 이벤트의 세부 사항 수준을 봐야 하는 경우에만 이를 보십시오.
블로킹 체인의 헤드에 있는 SPID의 쿼리 기간은 어느 정도입니까?
위의 완료된 이벤트를 포함시킬 경우 Duration 열은 쿼리 실행 시간을 보여 줍니다. 이를 사용하면 블로킹을 일으키는 장기간 실행되는 쿼리를 식별할 수 있습니다. 쿼리의 수행 속도가 느린 이유를 파악하려면 CPU, ReadWrites 열(Column)뿐만 아니라 Execution Plan 이벤트도 보십시오.

일반 블로킹 시나리오 분류

다음 표는 일반 증상과 그에 해당되는 원인을 나타냅니다. Scenario 열에 표시된 번호는 아래 문서의 "일반 블로킹 시나리오 및 해결책" 절에 있는 번호에 해당합니다. Waittype , Open_TranStatus 열(Column)은 sysprocesses 정보입니다. Resolves? 열(Column)은 블로킹이 자체적으로 해결되었는지 여부를 나타냅니다.

1 0이 아님 >= 0 runnable 예, 쿼리가 완료할 때입니다. Physical_IO, CPU 및/또는 Memusage 열(Column)은 시간이 지나면서 증가합니다. 쿼리의 기간은 완료될 때 높아집니다.
2 0x0000 >0 sleeping 아니오, 그러나 SPID는 중단됩니다. 이 SPID에 대해 Profiler(프로필러) 추적(Trace)에서 주의 신호를 볼 수 있는데 이는 쿼리 시간 제한 또는 취소가 발생하였음을 나타냅니다.
3 0x0000 >= 0 runnable 아니오. 클라이언트가 모든 행을 반입하거나 연결을 닫을 때까지 해결되지 않습니다. SPID는 중단될 수 있습니다. 그러나 중단하는데 30초 정도 걸립니다. open_tran = 0이고 SPID가 트랜잭션 격리 수준(Isolation Level)이 기본(READ COMMITTED)인 동안 잠금(Lock)을 걸고 있는데, 이것이 그 원인일 가능성이 있습니다.
4 다양화 >= 0 runnable 아니오. 클라이언트가 쿼리를 취소하거나 연결을 닫을 때까지 해결되지 않습니다. SPID는 중단될 수 있습니다. 그러나 중단하는데 30초 정도 걸립니다. 블로킹 체인의 헤드에 있는 SPID의 sysprocesseshostname 열(Column)은 블로킹 중인 SPID 중 하나와 같습니다.
5 0x0000 >0 rollback 예. 이 SPID의 Profiler(프로필러) 추적(Trace)에서 주의 신호를 볼 수 있는데, 이는 쿼리 시간 제한 또는 취소가 일어났거나 단순히 롤백 문이 실행되었음을 나타냅니다.
6 0x0000 >0 sleeping 결국, Windows NT가 세션이 더 이상 활성화되어 있지 않다고 확인하면 SQL Server 연결은 끊어집니다. sysprocesseslast_batch 값은 현재 시간보다 훨씬 앞섰습니다.

일반 블로킹 시나리오 및 해결책

아래에 나열된 시나리오에는 위의 표에 있는 특성이 있습니다. 이 절에서는 해당되는 경우 추가 세부 사항 및 해결 방법을 제공합니다.
1. 실행 시간이 긴 일반 실행 쿼리에 의한 블로킹

해결책:
이러한 유형의 블로킹 문제에 대한 해결책은 쿼리를 최저화하는 방법을 찾는 것입니다. 사실 이러한 유형의 블로킹 문제는 성능 문제일 수 있으므로 성능 문제의 관점에서 해결해야 합니다. 쿼리의 실행 시간이 오래 걸리는 문제를 해결하는 방법은 Microsoft 기술 자료에 있는 다음 문서를 참조하십시오.
243589 INF: Troubleshooting Slow-Running Queries on SQL Server 7.0
전반적인 응용 프로그램 성능 문제 해결에 대한 내용은 Microsoft 기술 자료에 있는 다음 문서를 참조하십시오.
224587 INF: Troubleshooting Application Performance with SQL Server
다른 사용자를 블로킹하는 실행 시간이 긴 쿼리가 있는데 이를 최적화할 수 없으면 이를 OLTP 환경에서 의사 결정 지원 시스템으로 이동하십시오.
2. 트랜잭션 중첩 수준(Nesting Level)의 추적(Trace)을 놓친 Sleeping 상태의 SPID에 의한 블로킹

이러한 블로킹 유형은 일시 정지(Sleeping)되었거나 명령을 기다리는(Waiting) SPID에서 식별될 수 있으나 트랜잭션 중첩 수준(Nesting Level)(@@TRANCOUNT, sysprocesses에서 open_tran)은 0보다 큽니다. 이는 응용 프로그램에서 쿼리 시간 제한을 겪거나 필요한 수만큼의 ROLLBACK 및/또는 COMMIT 문을 실행하지 않고 취소를 실행할 경우에도 발생합니다. SPID가 쿼리 시간을 제한하거나 취소를 받으면 이는 현재 쿼리 및 일괄 처리를 종료하지만 트랜잭션을 자동으로 롤백하거나 커밋하지 않습니다. SQL Server는 취소 중인 단일 쿼리로 인해 전체 트랜잭션을 단순히 롤백해야 한다고 간주할 수 없으므로 응용 프로그램은 이에 대한 책임이 있습니다. 쿼리 시간을 제한하거나 취소하는 것은 Profiler(프로필러) 추적(Trace)에서 SPID에 대해 ATTENTION 신호 이벤트로 나타납니다.

이를 표시하려면 Query Analyzer(쿼리 분석기)에서 다음과 같은 간단한 쿼리를 실행하십시오.

BEGIN TRAN
SELECT * FROM SYSOBJECTS S1, SYSOBJECTS S2

-- Issue this after canceling query
SELECT @@TRANCOUNT
ROLLBACK TRAN
쿼리가 실행 중인 동안 빨간색의 Cancel 단추를 누릅니다. 쿼리가 취소된 다음 SELECT @@TRANSCOUNT는 트랜잭션 중첩 수준(Nesting Level)이 하나임을 나타냅니다. 이것이 DELETE 또는 UPDATE 쿼리이거나 HOLDLOCK가 SELECT에 사용된 경우에는 얻은 잠금(Lock)이 계속 유지됩니다. 위의 쿼리에서조차 다른 쿼리를 얻고 트랜잭션 초기 잠금(Lock)을 건 경우에 위의 SELECT가 취소되어도 계속 유지됩니다.

해결책:

응용 프로그램은 제대로 트랜잭션 중첩 수준(Nesting Level)을 관리해야 하는데 그렇게 하지 않으면 이러한 방식으로 쿼리 취소 다음에 블로킹 문제를 일으킬 수 있습니다.
사실 이러한 유형의 블로킹 문제 또한 성능 문제일 수 있으므로 성능 문제의 관점에서 해결해야 합니다. 쿼리 실행 시간이 줄어들면 쿼리 시간 제한 또는 취소가 일어나지 않습니다. 시간 제한 또는 취소 시나리오가 발생할 때 응용 프로그램에서 이를 처리하는 것도 중요하지만 쿼리의 성능을 확인하여 이점을 얻을 수도 있습니다.

쿼리의 실행 시간이 오래 걸리는 문제를 해결하는 방법은 Microsoft 기술 자료에 있는 다음 문서를 참조하십시오.
243589 INF: Troubleshooting Slow-Running Queries on SQL Server 7.0
전반적인 응용 프로그램 성능 문제 해결에 대한 내용은 Microsoft 기술 자료에 있는 다음 문서를 참조하십시오.
224587 INF: Troubleshooting Application Performance with SQL Server
다른 사용자를 블로킹하는 실행 시간이 긴 쿼리가 있는데 이를 최적화할 수 없으면 이를 OLTP 환경에서 의사 결정 지원 시스템으로 이동하십시오.
3. 모든 결과 행을 완료로 반입하지 못한 해당 클라이언트 응용 프로그램의 SPID에 의한 블로킹

이 문제는 응용 프로그램을 제대로 설계하지 못해 일어납니다. 쿼리를 서버에 보낸 다음 모든 응용 프로그램은 모든 결과 행을 즉시 완료로 반입해야 합니다. 응용 프로그램에서 모든 결과 행을 반입하지 않으면 잠금(Lock)은 다른 사용자를 블로킹하면서 테이블에 남습니다. 투명하게 SQL 문을 서버에 제출하는 응용 프로그램을 사용하는 경우 응용 프로그램은 모든 결과 행을 반입해야 합니다. 그렇게 하지 않으면(그리고 이렇게 하도록 구성되어 있지 않으면) 블로킹 문제를 해결할 수 없습니다. 이러한 문제를 피하려면 제대로 작동하지 않는 응용 프로그램을 보고 또는 의사 결정 지원 데이터베이스로 제한할 수 있습니다.

해결책:

결과의 모든 행을 완료로 반입할 수 있도록 응용 프로그램을 다시 작성해야 합니다.
4. 분산 클라이언트/서버 교착 상태에 의한 블로킹

전형적인 교착 상태와는 달리 분산 교착 상태는 RDBMS 잠금(Lock) 관리자로 감지할 수 없습니다. 이는 교착 상태에 관련된 유일한 리소스는 SQL Server 잠금(Lock)이라는 사실 때문에 일어납니다. 교착 상태의 다른 측면은 클라이언트 응용 프로그램 수준에서 일어나는데, SQL Server는 이에 대해 제어할 수 없습니다. 다음은 이러한 문제가 발생할 수 있는 두 가지 예제 및 응용 프로그램에서 이를 피할 수 있는 방법입니다.

a. 단일 클라이언트 스레드와의 클라이언트/서버 분산 교착 상태
클라이언트에 열린 연결이 여러 개 있고 단일 실행 스레드가 있으면 다음과 같은 분산 교착 상태가 일어날 수 있습니다. 간단히 여기서 사용되는 "dbproc" 용어는 클라이언트 연결 구조를 의미합니다.

 SPID1------잠금(Lock)으로 ------->SPID2
  /\          블로킹됨        (클라이언트로 결과
  |                            기록 대기)
  |                                 |
  |                                 |                           서버측
  | ================================|==================================
  |     <-- 단일 스레드 -->         |                     클라이언트측
  |                                 \/
 dbproc1   <-------------------   dbproc2
 (다음 행                      (dbproc1에서 효과적으로 블로킹되어
  반입 대기)                    실행하기 위해 단일 스레드 실행 대기)
위의 경우 단일 클라이언트 응용 프로그램 스레드에는 열린 연결이 두 개 있습니다. 이는 비동기적으로 SQL 작업을 dbproc1에 제출합니다. 이는 진행하기 전에 반환하기 위해 호출을 기다리지 않음을 의미합니다. 그러면 응용 프로그램은 dbproc2에서 다른 SQL 작업을 제출하고 결과를 기다려 반환된 데이터를 처리합니다. 데이터가 되돌아 오기 시작하면(어떤 dbproc이 먼저 응답하든지 간에, 이를 dbproc1로 간주) 그 dbproc에 반환된 모든 데이터가 완료될 때까지 처리합니다. SPID1이 SPID2에서 걸고 있는 잠금(Lock)에서 블로킹될 때까지 두 쿼리가 서버에서 비동기로 실행되고 있으므로 dbproc1에서 결과가 반입됩니다. 이 때 dbproc1은 더 많은 데이터를 무한정 기다립니다. SPID2는 잠금(Lock)에서 블로킹되지만 데이터를 그 클라이언트인 dbproc2로 보내려고 합니다. 그러나 응용 프로그램 실행의 단일 스레드가 dbproc1에서 사용되므로 dbproc2는 응용 프로그램 계층의 dbproc1에서 효과적으로 블로킹됩니다. 리소스 중 SQL Server 리소스만 관련되어 있으므로 SQL Server가 감지하거나 해결할 수 없는 교착 상태가 발생합니다.
b. 각 연결마다 스레드가 있는 클라이언트/서버 분산 교착 상태

클라이언트의 각 연결마다 별도의 스레드가 있어도 이 분산 교착 상태의 변형이 다음에서와 같이 계속 발생합니다.

SPID1--------잠금(Lock)으로 -------->SPID2
  /\           블로킹됨        (네트 쓰기 시 대기)               서버측
  |                                 |
  |                                 |
  | INSERT                          |SELECT
  | ================================|==================================
  | <-- 각 dbproc 당 하나의  -->    |                      클라이언트측
  |           스레드                \/
 dbproc1   <-----데이터 행-----    dbproc2
 (삽입 시                      (dbproc1에서 블로킹되어 버퍼에서
  대기)                         행 읽는 작업 대기)
이 경우는 예제 A와 비슷합니다. 단, dbproc2 및 SPID2는 한번에 한 행씩 처리하고 동일한 테이블에서의 INSERT, UPDATE 또는 DELETE 문에 대해 각 행을 버퍼를 통해 dbproc1으로 전달할 의도를 가지고 SELECT 문을 실행하고 있습니다. 결국, SPID1(INSERT, UPDATE 또는 DELETE 수행)은 SPID2(SELECT 수행)에서 걸고 있는 잠금(Lock)으로 블로킹됩니다. SPID2는 클라이언트 dbproc2에 결과 행을 기록합니다. 그러면 Dbproc2는 버퍼에 있는 행을 dbproc1으로 전달하려고 하지만 dbproc1이 사용 중임을 알게 됩니다(이는 SPID2에 의해 블로킹된 SPID1에서 현재 INSERT를 완료하기를 기다리도록 블로킹됨) 이 때 dbproc2는 SPID(SPID1)가 SPID2에 의해 데이터베이스 수준에서 블로킹된 dbproc1에 의해 응용 프로그램 계층에서 블로킹됩니다. 리소스 중 SQL Server 리소스만 관련되어 있으므로 다시 한번 SQL Server가 감지하거나 해결할 수 없는 교착 상태가 발생합니다.
예제 A 및 B는 둘 다 응용 프로그램 개발자가 알고 있어야 하는 기본적인 문제입니다. 개발자는 이러한 경우를 제대로 처리할 수 있는 응용 프로그램을 작성해야 합니다.

해결책:

두 가지 확실한 해결책은 쿼리 시간 제한을 사용하거나 바운드 연결을 사용하는 것입니다.

쿼리 시간 제한
쿼리 시간 제한이 제공되었을 때 분산 교착 상태가 발생하면 시간 제한이 일어날 때 교착 상태가 중단됩니다. 쿼리 시간 제한 사용에 대한 자세한 내용은 DB-Library 또는 ODBC 설명서를 참조하십시오.
바운드 연결
이 기능을 통해 여러 개의 연결이 있는 클라이언트는 단일 트랜잭션 공간으로 바인드할 수 있으므로 연결은 서로 블로킹하지 않습니다. 자세한 내용은 SQL Server 7.0 온라인 설명서의 "Using Bound Connection" 항목을 참조하십시오.
5. "골든" 또는 롤백 상태에 있는 SPID에 의한 블로킹

중단되었거나 사용자 정의 트랜잭션 외부에서 취소된 데이터 수정 쿼리는 롤백됩니다. 이는 또한 클라이언트 컴퓨터가 다시 시작되고 해당 네트워크 세션 연결이 끊어지질 때 그 부작용으로서 발생합니다. 마찬가지로 교착 상태의 대상으로 선택된 쿼리는 롤백됩니다. 데이터 수정 쿼리는 종종 변경 사항이 초기에 적용되었을 때보다 더 빠르게 롤백될 수 없습니다. 예를 들어, DELETE, INSERT 또는 UPDATE 문이 몇 시간 동안 실행 중이었으면 롤백하는데 최소한 한 시간은 걸립니다. 변경 사항은 완전히 롤백되거나 데이터베이스에서의 트랜잭션 및 실제 무결성은 보완되므로 이는 예상했던 동작입니다. 이 동작은 반드시 발생해야 하므로 SQL Server는 SPID를 "골든" 또는 롤백(이를 중단하거나 교착 상태 대상으로 선택할 수 없음을 의미) 상태로 표시합니다. ROLLBACK 명령을 나타내는 sp_who의 출력을 관찰하여 이를 확인할 수 있습니다. sysprocessesStatus 열은 sp_who 출력 또는 SQL Enterprise Manager(엔터프라이즈 관리자) 현재 작업 화면에도 나타나는 ROLLBACK 상태를 나타냅니다. 이 정보를 볼 수 있는 가장 확실한 방법은 문제의 블로킹 SPID의 DBCC PSS를 조사하고 ec_stat 값을 관찰하는 것입니다. "골든" SPID는 ec_stat 값에 0x800를 가집니다. 기타 일반 ec_stat 값은 이 기사의 "DBCC PSS 출력을 확인합니다." 절에 나열되어 있습니다.

해결책:

SPID가 변경 사항을 롤백할 때까지 기다려야 합니다.

서버가 이 작업 중간에 종료되면 데이터베이스는 복구 모드에서 다시 시작되고 열린 모든 트랜잭션이 처리될 때까지 액세스할 수 없습니다. 시작 복구는 런타임 복구로서 트랜잭션마다 걸리는 것과 같은 시간이 걸리고 데이터베이스는 이 기간 동안 액세스될 수 없습니다. 그러므로, 서버를 강제로 종료하여 롤백 상태에 있는 SPID를 수정하면 성능이 저하됩니다.

이러한 상황을 피하려면 바쁜 시간에 OLTP 시스템에서 대규모 일괄 처리 INSERT, UPDATE 또는 DELETE 작업을 수행하지 마십시오. 가능하면 이러한 작업을 한가한 시간에 수행하도록 하십시오.
6. 고아가 된 연결에 의한 블로킹

클라이언트 응용 프로그램이 트랩되거나 클라이언트 워크스테이션이 다시 시작되면 서버에 대한 네트워크 세션은 몇몇 조건 하에서 즉시 취소되지 않을 수도 있습니다. 서버의 측면에서 클라이언트는 계속 존재하는 것으로 나타나고 얻은 모든 잠금(Lock)은 계속 유지되고 있는 것으로 나타납니다. 자세한 내용은 SQL Server 7.0 온라인 설명서에 있는 "Orphaned Connections" 항목을 참조하십시오.

해결책:

클라이언트 응용 프로그램이 해당 리소스를 제대로 정리하지 않고 연결이 끊어진 경우 KILL 명령을 사용하여 SPID를 종료할 수 있습니다. KILL 명령은 SPID 값을 입력으로 받습니다. 예를 들어, SPID 9를 중지하려면 다음 명령을 실행하면 됩니다.

KILL 9

참고: KILL 명령은 KILL 명령에 대한 검사 간의 간격으로 인해 완료하는데 30초 정도 걸립니다.

응용 프로그램의 블로킹 문제 관련

블로킹 문제가 발생했을 때 서버 측 조정 및 플랫폼 문제에 중점을 두는 경향이 있습니다. 그러나 이는 일반적으로 해결책을 제시하지는 않으며 클라이언트 응용 프로그램 및 제출하는 쿼리 검사에 기울일 수 있는 시간과 노력을 빼앗을 수 있습니다. 데이터베이스 호출에 대해 응용 프로그램에서 노출하는 가시성 수준에 관계없이 블로킹 문제에서는 응용 프로그램에서 제출한 정확한 SQL 문 및 쿼리 취소, 연결 관리, 모든 결과 행 반입 등에 관한 응용 프로그램의 동작이 정확한지 조사가 이루어져야 합니다. 개발 도구가 연결 관리, 쿼리 취소, 쿼리 시간 제한, 결과 반입 등에 대한 명시적인 제어를 허용하지 않은 경우 블로킹 문제를 해결할 수 없습니다. SQL Server, 특히 업무에 중요한 OLTP 환경에 대한 응용 프로그램 개발 도구를 선택하기 전에 이러한 가능성을 세밀하게 조사해야 합니다.

데이터베이스 및 응용 프로그램의 설계 및 구성 단계에서 이러한 상황을 매우 신중하게 검토해야 합니다. 특히, 리소스 사용, 격리 수준(Isolation Level) 및 트랜잭션 경로 길이를 각 쿼리마다 평가해야 합니다. 각 쿼리 및 트랜잭션은 가능한 한 라이트웨이트이어야 합니다. 적합한 연결 관리 규칙을 적용해야 합니다. 그렇게 하지 않으면 응용 프로그램은 사용자 수가 적을 때는 좋은 성능을 내는 것처럼 보이지만 사용자 수가 급증하면 성능이 확연하게 저하될 수 있습니다.

제대로 된 응용 프로그램 및 쿼리 설계를 통해 Microsoft SQL Server는 수천 명의 동시 사용자를 거의 블로킹하지 않고 단일 서버에서 지원할 수 있습니다. 자세한 내용은 SQL Server 7.0 온라인 설명서에 있는 "Application Design" 및 "Understanding and Avoiding Blocking" 항목을 참조하십시오. 수많은 사용자들이 접속하는 성공적인 사이트는 일반적으로 이 항목에서 설명한 기술을 사용합니다.

'MSSQL' 카테고리의 다른 글

(펌) MSSQL Server 백업 / 복구 시나리오  (0) 2011.10.20
(펌) MSSQL 2005 백업계획작성  (0) 2011.10.20
(펌) MSSQL Lock  (0) 2010.12.07
(펌) MSSQL2005 - New isolation level  (0) 2010.12.07
(펌) MSSQL SNAPSHOT 격리 수준  (0) 2010.12.07

+ Recent posts