출처 : http://support.microsoft.com/?id=271624
영문 : http://support.microsoft.com/?id=907877

INF: DBCC MEMORYSTATUS를 사용하여 SQL Server 메모리 사용 모니터

기술 자료 ID : 271624
마지막 검토 : 2004년 6월 29일 화요일
수정 : 1.0

요약

DBCC MEMORYSTATUS 명령은 SQL Server의 현재 메모리 상태에 대한 스냅샷을 제공합니다. 이 명령의 출력 결과는 SQL Server의 메모리 소비 또는 특정 메모리 부족 오류(오류 로그에서 이 출력을 자동으로 인쇄하는 많은 오류)와 관련된 문제를 해결하는 데 유용합니다. 또한 메모리 부족 조건과 관련이 있을 수 있는 오류가 발생한 경우에 특정 지원 문제 과정 동안 이 명령을 실행하도록 Microsoft 고객기술지원부에서 요청할 수 있습니다.

이 명령은 Microsoft 고객기술지원부를 위한 진단 도구입니다. 제공되는 출력 형식과 세부 정보 수준은 서비스 팩 및 제품 릴리스마다 바뀔 수 있습니다. 앞으로 출시될 제품 버전에서는 DBCC MEMORYSTATUS 명령에서 제공하는 기능이 다른 메커니즘으로 대체되어 이 명령이 더 이상 기능하지 않을 수도 있습니다. 이 명령을 변경하거나 제거할 때까지는 추가 경고가 나타나지 않습니다. 따라서, 이 동작에 의존하는 응용 프로그램이 경고 없이 중단될 수 있습니다.

DBCC MEMORYSTATUS의 출력 결과에는 7가지 섹션이 포함되어 있습니다. 이 문서에서는 처음 4섹션에 있는 출력 결과에서 조금씩 수집할 수 있는 키 데이터 중 몇 가지에 대해 설명합니다. 나머지 섹션은 독점적 구현 세부 정보를 포함하고 있지만 이 문서에 설명되어 있지 않습니다. Microsoft 고객기술지원부는 이 문서에 나와 있는 카운터 이외의 다른 카운터의 질문에 관한 답변이나 추가 정보를 제공하지 않습니다.

위로 가기

추가 정보

첫 번째 섹션

첫 번째 섹션에서는 버퍼 풀에 있는 8KB 버퍼의 배분에 대해 설명합니다. 버퍼 풀에 각 잠재적 8KB 페이지에 대해 버퍼 구조가 하나씩 있습니다. AWE(Address Windowing Extensions)가 사용 가능하게 설정된 경우에는 여기에 실제 페이지가 포함됩니다. 이 구조에는 기본 페이지의 상태(즉, 이 페이지가 수정되었는지, 고정되었는지, IO에 있는지 등)를 나타내는 비트 수가 포함되어 있습니다. 이 섹션에서는 특정 상태 비트를 갖고 있는 버퍼 수에 대해 상세히 설명합니다.
Buffer Distribution              Buffers     
------------------------------   ----------- 
Stolen                           241
Free                             95
Procedures                       89
Inram                            0
Dirty                            16
Kept                             0
I/O                              0
Latched                          18
Other                            880

(9 row(s) affected)
				
Stolen. Stolen 메모리는 정렬 또는 해시 작업에 사용 중인 버퍼(쿼리 작업 영역 메모리)나 잠금, 트랜잭션 컨텍스트 및 연결 정보 같은 내부 데이터 구조 저장용으로 할당하기 위해 일반 메모리 저장소로 사용 중인 버퍼를 나타냅니다. 버퍼 풀에서 Stolen 버퍼를 플러시하는 데 lazywriter 프로세스가 허용되지 않습니다.

Free. Free는 현재 사용되고 있지 않은 커밋된 버퍼를 나타냅니다. 데이터를 보유하는 데 사용할 수 있으며, 다른 구성 요소에서 요청해서 Stolen으로 표시될 수 있습니다.

Procedures. Procedures는 캐시된 저장 프로시저 메모리를 보유하고 있는 버퍼를 나타냅니다. 이 버퍼에는 프로시저와 캐싱된 임의 계획에 대한 컴파일된 계획과 실행 계획이 포함되어 있습니다.

Inram. Inram은 DBCC PINTABLE 명령이나 sp_tableoption 저장 프로시저를 사용하여 고정되어 메모리에서 제거할 수 없는 고정 테이블에 대한 페이지를 나타냅니다.

Dirty. Dirty는 아직 디스크로 플러시되지 않은 변경 내용이 있는 데이터 페이지를 나타냅니다.

Kept. Kept는 플러시되지 않도록 메모리에 임시로 고정된 페이지를 나타냅니다. SQL Server 2000에서는 이 값이 사용되지 않습니다.

I/O. I/O는 보류 중인 I/O 작업에서 대기 중인 버퍼를 나타냅니다. SQL Server 2000에서는 AWE를 통해 버퍼를 매핑하거나 매핑을 해제하는 것도 여기에 포함됩니다.

Latched. 걸쇠(latched) 버퍼는 연결이 해당 페이지에서 행을 읽거나 수정하고 있는 버퍼 수를 기록합니다. 걸쇠는 페이지의 데이터를 읽거나 수정하는 동안 데이터의 물리적 일관성을 확인하는 데 사용되지만, 잠금은 논리적 및 트랜잭션 일관성을 확인하는 데 사용됩니다.

Other. 앞에서 설명한 기준을 충족시키지 못하는 커밋된 페이지입니다. 일반적으로, 이 기준을 충족시키는 버퍼 중 대다수는 버퍼 캐시에 있는 해시된 데이터와 인덱스 페이지입니다.

위로 가기

두 번째 섹션

두 번째 섹션에는 버퍼 풀의 전체 크기와 구성을 반영하는 몇 가지 합계의 목록이 나와 있습니다.
Buffer Counts                  Buffers     
------------------------------ ----------- 
Committed                      1339
Target                         6318
Hashed                         914
InternalReservation            120
ExternalReservation            0
Min Free                       16

(6 row(s) affected)
				
Committed. Microsoft Windows NT에서 커밋된 전체 버퍼 수입니다. Committed 값은 버퍼 풀의 현재 "크기"에 해당합니다. AWE가 사용 가능하게 설정된 경우 이 값에는 할당된 실제 메모리도 포함됩니다.

Target. 목표 Committed 수 또는 해당 버퍼 풀에 대해 원하는 크기입니다. Target 수가 Committed 값보다 크면 버퍼 풀의 크기가 계속 증가하는 것입니다. Target 수가 Committed 값보다 작으면 버퍼 풀의 크기가 계속 감소하는 것입니다. lazywriter 프로세스는 이 값을 사용하여 해당 버퍼 풀의 크기를 증가시킬 것인지 아니면 감소시킬 것인지 결정합니다.

Hashed. 해당 버퍼 풀에 저장되어 있는 데이터 및 인덱스 페이지 수입니다.

InternalReservation. 버퍼 풀이 자신을 위해 보유하고 있는 페이지 수입니다. InternalReservation 값은 현재 사용자 수에 대해 간단한 데이터 요청을 만족시키는 데 필요한 최소 페이지 수입니다.

ExternalReservation. 정렬 또는 해시 작업을 수행할 쿼리용으로 예약되었지만 아직 빼앗기지 않은 페이지 수입니다. 이 값은 다량의 메모리에 대한 요구가 곧 있을 것임을 lazywriter에 알리기 위한 힌트로 사용됩니다.

Min Free. 해당 버퍼 풀이 사용 가능한 페이지 목록에서 유지하려고 하는 페이지 수입니다. 사용 가능한 페이지 목록이 Min Free 값 아래로 떨어지면 버퍼 풀이 데이터 캐시에서 오래된 페이지를 제거하거나 프로시저 캐시에서 오래된 프로시저를 제거하여 사용 가능한 페이지 목록을 채우려고 합니다.

위로 가기

세 번째 섹션

세 번째 섹션에서는 프로시저 캐시의 구성에 대해 설명합니다.
Procedure Cache                Value       
------------------------------ ----------- 
TotalProcs                     17
TotalPages                     89
InUsePages                     40

(3 row(s) affected)
				
TotalProcs. 해당 프로시저 캐시에 현재 있는 캐시된 개체의 총 수입니다. 이 값은 syscacheobjects 가상 테이블의 항목 수와 일치합니다. 성능 모니터를 사용하여 SQL Server:Cache Manager 개체를 모니터해서 캐시된 개체의 유형(예: 트리거, 프로시저 및 임의)을 상세하게 분석할 수 있습니다.

TotalPages. 캐시된 개체 모두를 저장하기 위해 갖고 있어야 할 누적 페이지 수입니다.

InUsePages. 현재 실행 중인 프로시저에 속해 있는 프로시저 캐시의 페이지 수입니다. 이 페이지는 삭제할 수 없습니다.

위로 가기

네 번째 섹션

네 번째 섹션에서는 해당 서버에 있는 주요 구성 요소 그룹 간에 빼앗긴 버퍼를 상대적으로 배분하는 것에 대해 설명합니다. SQL Server는 작은 할당에 버퍼 풀 페이지를 사용하고 약 8KB를 초과하는 할당에 대해서만 운영 체제 루틴으로 바뀌는 자체 메모리 관리자를 구현합니다. 이렇게 8KB보다 큰 할당은 버퍼 풀 외부에서 이루어지는데, 보통 이를 MemToLeave 영역에서의 할당이라고 합니다. 이 영역의 크기는 -g 명령줄 옵션을 사용하여 제어할 수 있습니다.

일반적으로, MemToLeave에서의 메모리 할당 중 대부분은 COM 개체, 확장 저장 프로시저 및 연결된 서버 같은 프로세스 내에서 실행 중인 비 SQL Server 메모리 소비자에서 이루어집니다. DBCC MEMORYSTATUS에서 보고되는 운영 체제 관련 값만이 SQL Server에서 직접 수행하는 MemToLeave 할당에 영향을 미칩니다. 그 밖의 다른 구성 요소에서의 MemToLeave에서 발생하는 다른 할당은 SQL Server가 이러한 메모리 요청에 대한 지식이 없기 때문에 포함되지 않습니다.
Dynamic Memory Manager         Buffers     
------------------------------ ----------- 
Stolen                         330
OS Reserved                    152
OS Committed                   138
OS In Use                      129
General                        322
QueryPlan                      87
Optimizer                      0
Utilities                      10
Connection                     40

(9 row(s) affected)
				
Stolen. 5가지 메모리 관리자(General, Query Plan, Optimizer, Utilities, Connection)가 버퍼 풀에게서 빼앗은 전체 버퍼 수입니다. 이들 페이지는 8KB보다 작은 메모리 할당을 서비스하는 데 사용됩니다.

OS Reserved. 운영 체제로부터 약 8KB보다 큰 할당 요청을 처리하기 위해 예약된 페이지 수입니다.

OS Committed. 예약된 영역에서 커밋된 메모리의 양입니다. 버퍼가 먼저 예약되고 그 다음에 커밋되기 때문에 OS Committed 값이 OS Reserved 값보다 작거나 같아야 합니다. 메모리 중 일부는 더 이상 사용되지 않을 경우 커밋 해제될 수도 있습니다.

참고 SQL Server 2000에는 버그가 있는데 이로 인해 이 값이 정확하게 유지되지 않아 유효 범위를 벗어날 수 있습니다. 이 버그는 SQL Server 2000 서비스 팩 3에서 해결되었습니다.

OS In Use. 처리되지 않은 메모리 할당을 현재 백업하는 중인 OS Committed 버퍼 수입니다.

General. 구문 분석 또는 정규화, 잠금, 트랜잭션 컨텍스트, 테이블 및 인덱스에 대한 메모리 내 메타 데이터를 설명하는 내부 데이터 구조 및 기타를 포함하여 서버의 일반 메모리 소비자가 사용하는 메모리입니다. 이 값은 8KB 버퍼의 전체 수에 해당합니다. 이 중 일부는 버퍼 풀의 빼앗긴 버퍼이고, 또 다른 일부는 MemToLeave에서 할당된 버퍼입니다.

QueryPlan. 쿼리 계획을 일시적으로 또는 캐시에 저장하는 데 사용되는 메모리입니다. 이 값은 8KB 버퍼의 전체 수에 해당합니다. 이 중 일부는 버퍼 풀의 빼앗긴 버퍼이고, 또 다른 일부는 MemToLeave에서 할당된 버퍼입니다.

Optimizer. 쿼리 최적화 프로그램에서 사용되는 메모리입니다. 이 값은 8KB 버퍼의 전체 수에 해당합니다. 이 중 일부는 버퍼 풀의 빼앗긴 버퍼이고, 또 다른 일부는 MemToLeave에서 할당된 버퍼입니다.

Utilities. BCP, 로그 관리자, 병렬 쿼리, ::fn_trace_gettable 등과 같은 여러 가지 유틸리티 루틴에서 사용 중인 메모리입니다. 이 값은 8KB 버퍼의 전체 수에 해당합니다. 이 중 일부는 버퍼 풀의 빼앗긴 버퍼이고, 또 다른 일부는 MemToLeave에서 할당된 버퍼입니다.

Connection. 각 사용자 연결에 대한 연결 컨텍스트를 보유하고 있는 데이터 구조를 저장하는 데 사용되는 메모리입니다. 또한 여기에는 연결과 관련이 있는 네트워크 보내기 및 받기 버퍼에 대한 메모리도 포함되어 있습니다. 이 값은 8KB 버퍼의 전체 수에 해당합니다. 이 중 일부는 버퍼 풀의 빼앗긴 버퍼이고, 또 다른 일부는 MemToLeave에서 할당된 버퍼입니다.

위로 가기

참조

SQL Server Books Online
Delaney, Kalen. Inside Microsoft SQL Server 2000, Microsoft Press, 2000.



Microsoft 제품 관련 기술 전문가들과 온라인으로 정보를 교환하시려면 Microsoft 뉴스 그룹 (http://support.microsoft.com/newsgroups/default.aspx)에 참여하시기 바랍니다.

위로 가기


본 문서의 정보는 다음의 제품에 적용됩니다.
Microsoft SQL Server 2000 Standard Edition
Microsoft SQL Server 7.0 Standard Edition

위로 가기

키워드:
kbinfo KB271624

위로 가기

AND

출처 : http://dbguide.net/dbqa/dbqa120001.jsp?mode=view&divcateno=259&divcateno_=244&pg=1&idx=36

Monitor Database Growth on Multiple SQL Servers
By
Muthusamy Anantha Kumar aka The MAK


번역 : 김종균


담당하는 모든 SQL서버의 데이터증가량을 모니터링하는것을 SQL서버 관리자들에겐
매우중요한 일이다. 용량증가같은 모니터링을 통하여, 데이터베이스 관리자는 디스크 볼륨
확장, 다른 위치로의 .mdf, .ndf 파일이동, 데이터베이스의 증가 예측등과 같은 적절한 선행조치를
취할수 있을것이며 이를 통하여 데이터베이스 사이즈는 적당한 때에 확장되어 질것이다.

이 글의 주목적은 주기적으로 물리적 데이터베이스 파일의 사이즈를 테이블로 저장할것인가에 대한
기본적 생각을 제시하기위함이다. 이로써 모든 SQL서버들의 모든 데이터베이스의 사이즈와 증가량을
모니터 할수 있을껏이다. 또한 SQL서버들의 디스크 사이즈도 모니터링 할수 있을 것이다.


단계1. Create Database
데이터베이스의 증가치를 모니터하기위해 우리는 하나의 데이터베이스와
정보를 저장하기위한 몇몇 테이블이 필요하다.
아래에 제공된 SQL 코드를 사용하여 모니터링에 필요한 데이터베이스와 적절한 테이블들을 생성하고
모든 SQL서버의 이름이 'Servers'테이블에 저장되었는지 확인하라.

-----SQL Code----------------
Use master
Go
Create Database DBMonitor
go
use DBMonitor
go
sp_addlogin 'DBmonitoruser','monitor','DBMonitor'
go
sp_adduser 'DBMonitoruser'
go
sp_Addrolemember 'db_datawriter','DBMonitoruser'
go
sp_Addrolemember 'db_datareader','DBMonitoruser'
go
Create table Servers (id int identity (1,1), Servername varchar(256))
go
insert into servers (Servername) select 'SQL'
insert into servers (Servername) select 'YUKON'
insert into servers (Servername) select 'ETL'
go
CREATE TABLE DBGrowth (
[Servername] [varchar](128),
[Databasename] [varchar](128),
[timestamps] [datetime],
[fileid] [smallint] NOT NULL ,
[groupid] [smallint] NOT NULL ,
[size] [int] NOT NULL ,
[maxsize] [int] NOT NULL ,
[growth] [int] NOT NULL ,
[status] [int] NOT NULL ,
[perf] [int] NOT NULL ,
[name] [nchar] (128) ,
[filename] [nchar] (260) 
)
go
Create table DiskSpace ([Servername] [varchar] (128),
[timestamps] [datetime],
[Drive] [varchar] (2),
[MBFree] [int])
go



단계2. Create VB SCript
'c:\DBMonitor' 라는 폴더를 만들고 아래의 VB스크립트파일을 그 폴더로 복사하자.


-----VB Script Code------------------------
'Author: MAK
'Contact:
mak_999@yahoo.com
'Objective: To monitor Database Size and Growth of all SQL Server databases
Dim AdCn
Dim AdRec
Dim i, SQL
Set AdCn = CreateObject("ADODB.Connection")
Set AdRec = CreateObject("ADODB.Recordset")
Set AdCn2 = CreateObject("ADODB.Connection")
Set AdRec2 = CreateObject("ADODB.Recordset")
'Change the below connection parameters according to your environment.
AdCn.Open = "Provider=SQLOLEDB.1;Data
  Source= SQL;Initial Catalog=DBMonitor;user
  id = 'dbmonitoruser';password='monitor' "
SQL1 = "Select ServerName from Servers"
AdRec.Open SQL1, AdCn,1,1
while not Adrec.EOF
computername=Adrec("ServerName")
'msgbox computername
connectionstring="Provider=SQLOLEDB.1;
  Data Source= " +computername+";
  Initial Catalog=master;
  Integrated Security=SSPI"
AdCn2.open= connectionstring
'msgbox connectionstring
SQL2="Begin CREATE TABLE tempdb.dbo.DbGrowth
  ([Servername] [varchar](128),
  [Databasename] [varchar](128),
  [timestamps] [datetime],
  [fileid] [smallint] NOT NULL ,
  [groupid] [smallint] NOT NULL ,
  [size] [int] NOT NULL ,
  [maxsize] [int] NOT NULL ,
  [growth] [int] NOT NULL ,
  [status] [int] NOT NULL ,
  [perf] [int] NOT NULL ,
  [name] [nchar] (128) ,
  [filename] [nchar] (260)   )
END BEGIN exec master.dbo.sp_msforeachdb
  $insert tempdb.dbo.DbGrowth
  select @@servername as Servername,'?' as Databasename,
  getdate() as TimeStamps, * from ?.dbo.sysfiles$
END begin Create table tempdb.dbo.DiskSpace
  (Servername varchar(128),
  timestamps datetime,
  Drive varchar(2),
  MBFree int)
end begin insert tempdb.dbo.diskspace (Drive,MBFree)
  exec master.dbo.xp_fixeddrives
end begin update tempdb.dbo.diskspace set
  servername = @@servername, timestamps =getdate() end"
SQL2=replace(SQL2,"$",chr(34))
'msgbox SQL2
rec=0
'AdCn2.execute SQL2, rec, adCmdtext
AdCn2.execute SQL2
Adrec2.open "Select * from tempdb.dbo.DiskSpace",Adcn2,1,1

while not Adrec2.EOF
sqlx2 ="insert into diskspace(Servername,timestamps,Drive,MBFree)
  values('" & Adrec2("Servername") & "',
  '" & Adrec2("timestamps")  & "',
  '" & Adrec2("Drive") & "',
  " &  Adrec2("MBFree") & ")"
'msgbox   sqlx2

'AdCn.execute SQLx2, rec, adCmdtext
AdCn.execute SQLx2

Adrec2.movenext
wend
Adrec2.close

Adrec2.open "Select * from tempdb.dbo.DBgrowth",Adcn2,1,1

while not Adrec2.EOF
sqlx ="insert into dbgrowth
  (Servername,Databasename,timestamps,fileid,groupid,size,maxsize,growth,status,perf,name,filename)
  values ('" & Adrec2("Servername") & "',
    '" & Adrec2("Databasename")  & "',
 '"  & Adrec2("timestamps")  &  "',
 " & Adrec2("fileid") & ",
 " & Adrec2("groupid") & ",
 "  & Adrec2("size") & ",
 " & Adrec2("maxsize") & ",
 " & Adrec2("growth") & ",
 " & Adrec2("status") & ",
 " & Adrec2("perf") & ",
 '" & Adrec2("name") & "',
 '" & Adrec2("filename") & "')"
'msgbox   sqlx
'AdCn.execute SQLx, rec, adCmdtext
AdCn.execute SQLx
Adrec2.movenext
wend
Adrec2.close
AdCn2.execute "drop table tempdb.dbo.DbGrowth"
AdCn2.execute "drop table tempdb.dbo.DiskSpace"
AdCn2.close

Adrec.movenext
wend

' Remove the code for message box if you are scheduling this vbs
msgbox "DB Monitor Completed"



단계3. Execute the VB Script
위 단계에서 작성된 VB스크립트가 실행되면, 모든 서버의 디스크사이즈 정보와, .mdf, .ndf, .ldf 파일들의
모든 정보가 'DBMonitor'데이터베이스의 'DBGrowth'와 'DiskSpace'테이블에 저장된다.
VB스크립트가 수행되고 나면 'DB Monitor Completed'라는 메세지 박스가 뜨는데, 스케쥴링 작업에 이 스크립트를
사용하려면 메세지 박스를 띄우는 부분의 코드는 빼길바란다.
Note. 이 VB스크립트를 사용하기 위해서 'Servers'테이블에 저장된 모든 SQL서버들에 접근할수 있는 충분한 권한이 있는지
NT login정보를 꼭 확인해야만 한다. 이 VB스크립트를 스케줄링 할때도 모든 SQL서버들에 액세스할 충분한 권한이 있는 계정인지
확인해야한다.



단계4. Query the Database tables
아래의 쿼리는 모든 SQL서버들의 디스크공간에 관한 정보를 나타낼것이다.

Use DBMonitor
Go
select * from Diskspace order by timestamps
go

Results

YUKON

3/15/04 7:14 PM

C

15058

YUKON

3/15/04 7:14 PM

D

17101

SQL

3/15/04 7:16 PM

C

1492

SQL

3/15/04 7:16 PM

D

4351

SQL

3/15/04 7:16 PM

F

45382

SQL

3/15/04 7:16 PM

G

43922

ETL

3/15/04 7:16 PM

D

4946

ETL

3/15/04 7:16 PM

C

1039

YUKON

3/16/04 3:00 AM

C

15058

YUKON

3/16/04 3:00 AM

D

17101

SQL

3/16/04 3:02 AM

D

4351

SQL

3/16/04 3:02 AM

F

45042

SQL

3/16/04 3:02 AM

G

43922

SQL

3/16/04 3:02 AM

C

1448

ETL

3/16/04 3:02 AM

C

1039

ETL

3/16/04 3:02 AM

D

4946




Use DBMonitor
Go
select Servername, Databasename, size, timestamps,
  filename from DBGrowth order by timestamps, servername, Databasename

Results

YUKON

AdventureWorks

8448

3/15/04 7:14 PM

C:\Program Files\Microsoft SQL Server\
MSSQL.1\MSSQL\DATA\
AdventureWorks_data.mdf

YUKON

AdventureWorks

96

3/15/04 7:14 PM

C:\Program Files\Microsoft SQL Server\
MSSQL.1\MSSQL\DATA\
AdventureWorks_log.ldf

SQL

Bank

88

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Bank.mdf

SQL

Bank

63

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Bank_log.LDF

SQL

DBMonitor

80

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\DBMonitor.mdf

SQL

DBMonitor

63

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\DBMonitor_log.LDF

ETL

analyse

80

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\analyse.mdf

ETL

analyse

63

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\analyse_log.LDF

ETL

Journal

63

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Journal_log.LDF

ETL

Journal

80

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Journal.mdf

ETL

Walker

80

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Walker.mdf

ETL

Walker

63

3/15/04 7:16 PM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Walker_log.LDF

YUKON

AdventureWorks

8448

3/16/04 3:00 AM

C:\Program Files\Microsoft SQL Server\
MSSQL.1\MSSQL\DATA\
AdventureWorks_data.mdf

YUKON

AdventureWorks

96

3/16/04 3:00 AM

C:\Program Files\Microsoft SQL Server\
MSSQL.1\MSSQL\DATA\
AdventureWorks_log.ldf

SQL

Bank

88

3/16/04 3:02 AM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Bank.mdf

SQL

Bank

63

3/16/04 3:02 AM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Bank_log.LDF

SQL

DBMonitor

63

3/16/04 3:02 AM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\DBMonitor_log.LDF

SQL

DBMonitor

112

3/16/04 3:02 AM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\DBMonitor.mdf

ETL

analyse

80

3/16/04 3:02 AM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\analyse.mdf

ETL

analyse

63

3/16/04 3:02 AM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\analyse_log.LDF

ETL

Walker

80

3/16/04 3:02 AM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Walker.mdf

ETL

Walker

63

3/16/04 3:02 AM

D:\Program Files\Microsoft SQL Server\
MSSQL\data\Walker_log.LDF



단계5. Analyze the data in tables
일정기간동안의 사이즈정보를 수집하여 디스크사이즈의 변화와 크로스탭 쿼리 생성을 통한
데이터베이스 증가치의 데이터를 분석하라.

--Daily Growth
Select ServerName,DatabaseName,
 SUM(CASE WHEN convert(varchar(12),timestamps,110) = '03-15-2004'
   THEN Size ELSE 0 END) AS '03-15-2004' ,
 SUM(CASE WHEN convert(varchar(12),timestamps,110) = '03-16-2004'
   THEN Size ELSE 0 END) AS '03-16-2004'
from DBGrowth group by ServerName,DatabaseName


Results

Servername

Databasename

3/15/2004

3/16/2004

YUKON

AdventureWorks

9824

9825

YUKON

AdventureWorksDW

7743

7749

ETL

analyse

143

142

SQL

ArrowPack

768

868

SQL

Bank

151

156

ETL

CM_Sales

4816

4818



--Average Growth for every week
Select ServerName,DatabaseName,
 SUM(CASE WHEN datepart(wk,timestamps) = 1 THEN Size ELSE 0 END)/7 AS 'Week1' ,
 SUM(CASE WHEN datepart(wk,timestamps) = 2 THEN Size ELSE 0 END)/7 AS 'Week2' ,
 SUM(CASE WHEN datepart(wk,timestamps) = 3 THEN Size ELSE 0 END)/7 AS 'Week3'
from DBGrowth group by ServerName,DatabaseName


-- Average Growth for every year
Select ServerName,DatabaseName,
 SUM(CASE WHEN year(timestamps) = 2001 THEN Size ELSE 0 END)/365 AS '2001' ,
 SUM(CASE WHEN year(timestamps) = 2002 THEN Size ELSE 0 END)/365 AS '2002' ,
 SUM(CASE WHEN Year(timestamps) = 2003 THEN Size ELSE 0 END)/365 AS '2003' ,
 SUM(CASE WHEN Year(timestamps) = 2004 THEN Size ELSE 0 END)/365 AS '2004'
from DBGrowth group by ServerName,DatabaseName


어떻게 수행되는가 ?
기본적으로, 그 VB스크립트는 모든 SQL서버의 'tempdb'에 'DBGrowth'와'DiskSpace'를 생성한다.
그리고 아래표시된 두개의 쿼리가  수행되면 그 임시 테이블들에 결과값이 저장되어진다. 그런다음
 두개의 임시테이블을 쿼리하여 그 결과값을 'DBMonitor' 데이터베이스의 'DBGrowth'와 'DiskSpace'테이블에
저장한다. 그리고 끝으로 'tempdb'에 생성된 두개의 테이블은 삭제된다.

Exec master.dbo.sp_msforeachdb
  "insert tempdb.dbo.DbGrowth select @@servername as Servername,
  '?' as Databasename,getdate() as TimeStamps, * from ?.dbo.sysfiles"

Exec master.dbo.xp_fixeddrives


결론
처음에 언급했듯이, 이번 글의 주목적은 데이터베이스의 물리적 파일사이즈를 어떻게 찾고 주기적으로 저장할지에 관한
기본적인 아이디어를 제공하기 위함이다. 이를 통해 당신은 관리하는 모든 SQL서버들의 데이터베이스의 크기와 증가량을
모니터 할수 있을것이며, 테이블에 저장된 모니터정보를 활용하여 간단한 웹페이지 작성할수 도 있을것이며 그래픽하게 표시된
 데이터베이스 증가치를 온라인으로 볼 수도 있을것이다.



출처명 : http://www.databasejournal.com
AND

출처 : http://support.microsoft.com/kb/186133/ko


SELECT Transact-SQL 문에서 동적으로 행 번호를 매기는 방법

기술 자료 ID : 186133
마지막 검토 : 2006년 5월 22일 월요일
수정 : 5.1
이 문서는 이전에 다음 ID로 출판되었음: KR186133

요약

이 문서에서는 유연성 있는 방법을 사용하여 SELECT 문을 수행할 때 동적으로 행의 순위를 매기는 방법을 설명합니다. 이 방법은 유일한 해결책일 수 있으며 프로시저 해결책보다 빠릅니다. 행 번호 매기기나 순위 매기기는 전형적인 프로시저 문제입니다. 이 해결책은 대개 루프와 임시 테이블을 기반으로 하므로 SQL Server 루프와 커서를 사용합니다. 이 기술은 자동 조인에 기반을 두고 있습니다. 선택되는 관계는 대개 "보다 큼"입니다. 특정 데이터 집합을 자신과 비교할 때 해당 집합의 각 요소가 "보다 큼" 관계를 충족하는 횟수를 계산합니다.

참고 다음 예제는 pubs 데이터베이스를 기반으로 합니다. 기본적으로 Northwind 예제 데이터베이스와 pubs 예제 데이터베이스는 SQL Server 2005에 설치되지 않습니다. 이러한 데이터베이스는 Microsoft 다운로드 센터에서 다운로드할 수 있습니다. 자세한 내용을 보려면 다음 Microsoft 웹 사이트를 방문하십시오.
http://go.microsoft.com/fwlink/?linkid=30196 (http://go.microsoft.com/fwlink/?linkid=30196)(영문)
SQL2000SampleDb.msi를 다운로드한 후 SQL2000SampleDb.msi를 두 번 눌러 예제 데이터베이스 스크립트를 추출합니다. 기본적으로 SQL2000SampleDb.msi는 데이터베이스 스크립트와 추가 정보 파일을 다음 폴더에 추출합니다.
C:\SQL Server 2000 Sample Databases
추가 정보 파일의 지침에 따라 설치 스크립트를 실행합니다.

위로 가기

SQL Server 2005를 사용하는 경우

SQL Server 2005에서 새 기능으로 제공되는 순위 함수를 사용하는 것이 좋습니다. 순위 함수에 대한 자세한 내용은 다음 MSDN(Microsoft Developer Network) 웹 사이트를 참조하십시오.
http://msdn2.microsoft.com/ko-kr/library/ms189798.aspx (http://msdn2.microsoft.com/ko-kr/library/ms189798.aspx)

위로 가기

예제 1

이 예제의 경우
집합 1은 저자입니다.
집합 2는 저자입니다.
관계는 "성과 이름이 보다 큼(last and first names are greater than)"입니다.
이름 + 성을 다른 이름 + 성과 비교하여 중복 문제를 방지할 수 있습니다.
count(*)를 통해 해당 관계가 충족된 횟수를 계산합니다.
쿼리:
   select rank=count(*), a1.au_lname, a1.au_fname
   from authors a1, authors a2
   where a1.au_lname + a1.au_fname >= a2.au_lname + a2.au_fname
   group by a1.au_lname, a1.au_fname
   order by rank
				
SQL Server 2005에서는 다음 코드를 사용합니다.
   select rank() OVER (ORDER BY a.au_lname, a.au_fname) as rank, a.au_lname, a.au_fname
   from authors a
   order by rank 
결과:
   Rank        Au_Lname              Au_Fname
   ----        --------------        -----------
     1         Bennet                Abraham
     2         Blotchet-Halls        Reginald
     3         Carson                Cheryl
     4         DeFrance              Michel
     5         del Castillo          Innes
     6         Dull                  Ann
     7         Greene                Morningstar
     8         Green                 Marjorie
     9         Gringlesby            Burt
    10         Hunter                Sheryl
    11         Karsen                Livia
    12         Locksley              Charlene
    13         MacFeather            Stearns
    14         McBadden              Heather
    15         O'Leary               Michael
    16         Panteley              Sylvia
    17         Ringer                Albert
    18         Ringer                Anne
    19         Smith                 Meander
    20         Straight              Dean
    21         Stringer              Dirk
    22         White                 Johnson
    23         Yokomoto              Akiko

   (23 row(s) affected)
				

위로 가기

예제 2

이 예제의 경우
판매 부수를 기준으로 서점의 순위를 매깁니다.
집합 1은 서점에서 판매한 책 부수입니다(select stor_id, qty=sum(qty) from sales group by stor_id).
집합 2는 서점에서 판매한 책 부수입니다(select stor_id, qty=sum(qty) from sales group by stor_id).
관계는 "책 부수가 보다 큼(the number of books is greater than)"입니다.
중복을 피하기 위해 qty 대신 price*qty를 비교할 수도 있습니다.
쿼리:
   select rank=count(*), s1.stor_id, qty=sum(s1.qty)
   from (select stor_id, qty=sum(qty) from sales group by stor_id) s1,
        (select stor_id, qty=sum(qty) from sales group by stor_id) s2
   where s1.qty >= s2.qty
   group by s1.stor_id
   order by rank
				
결과:
   Rank     Stor_Id    Qty
   ----     -------    ---
   1         6380        8
   2         7896      120
   3         8042      240
   4         7067      360
   5         7066      625
   6         7131      780

   (6 row(s) affected)
				
참고 Qty 열의 값이 올바르지 않지만 판매 부수를 기준으로 하는 서점 순위는 올바릅니다. 이것이 이 방법의 결함입니다. 결과에 부수가 잘못 나타나는 것에 신경쓰지 않는다면 이 방법을 사용하여 서점의 순위를 반환할 수 있습니다.

SQL Server 2005에서는 다음 코드를 사용합니다.
select row_number() over (order by qty desc) as rank,s1.stor_id,s1.qty
from (select stor_id, qty=sum(qty) from sales group by stor_id) as s1
결과:
rank     stor_id  qty
-------  -------  ------
1        7131     130
2        7066     125
3        7067     90
4        8042     80
5        7896     60
6        6380     8

(6 row(s) affected)
참고 SQL Server 2005에서 순위 함수를 사용하면 순위와 부수에 대한 올바른 결과를 얻을 수 있습니다.

위로 가기

예제 3

이 예제의 경우
수익을 기준으로 출판사의 순위를 매깁니다.
집합 1은 출판사별 총 판매액입니다.
        select t.pub_id, sales=sum(s.qty*t.price)
        from sales s, titles t
        where s.title_id=t.title_id
          and t.price is not null
        group by t.pub_id
					
집합 2는 출판사별 총 판매액입니다.
        select t.pub_id, sales=sum(s.qty*t.price)
        from sales s, titles t
        where s.title_id=t.title_id
          and t.price is not null
        group by t.pub_id
					
관계는 "보다 많은 돈을 벌었음(earns more money than)"입니다.
쿼리:
   select rank=count(*), s1.pub_id, sales=sum(s1.sales)
   from    (select t.pub_id, sales=sum(s.qty*t.price)
           from sales s, titles t
           where s.title_id=t.title_id
             and t.price is not null
           group by t.pub_id) s1,
           (select t.pub_id, sales=sum(s.qty*t.price)
           from sales s, titles t
           where s.title_id=t.title_id
             and t.price is not null
           group by t.pub_id) s2
   where s1.sales>= s2.sales
   group by s1.pub_id
   order by rank
				
결과:
   Rank     Pub_Id   Sales
   ----     ------   --------
   1         0736    1,961.85
   2         0877    4,256.20
   3         1389    7,760.85

   (3 row(s) affected)
				
참고 Sales 열의 값이 올바르지 않지만 수익을 기준으로 하는 출판사의 순위는 올바릅니다.

SQL Server 2005에서는 다음 코드를 사용합니다.
select rank() over (order by sales desc) as rank,s1.pub_id,s1.sales 
from (select t.pub_id, sales=sum(s.qty*t.price)
     from sales s inner join titles t
     on s.title_id=t.title_id
     where  t.price is not null
     group by t.pub_id) as s1
결과:
rank     pub_id  sales
-------  ------  ---------
1        1389    2586.95
2        0877    2128.10
3        0736    1961.85

(3 row(s) affected)
				
참고 SQL Server 2005에서 순위 함수를 사용하면 순위와 수익에 대한 올바른 결과를 얻을 수 있습니다.

위로 가기

단점

Cross Join 때문에 많은 수의 행에는 이 방법을 사용할 수 없습니다. 이 방법은 행이 수십 또는 수백 개인 경우에 적합합니다. 대형 테이블에서는 대규모 검색을 피하기 위해 인덱스를 사용해야 합니다.
이 방법은 중복 값을 제대로 처리하지 못합니다. 중복 값을 비교하면 행 번호가 불연속적으로 매겨집니다. 행 번호가 불연속적으로 매겨지는 것을 원하지 않으면 스프레드시트에 결과를 삽입할 때 순위 열을 숨기고 대신 스프레드시트 번호 매기기를 사용하면 됩니다.

참고 SQL Server 2005를 사용하는 경우 row_number() 함수를 사용하여 중복 행에 관계없이 순차적인 행의 번호를 반환할 수 있습니다.
예제:
   select rank=count(*), s1.title_id, qty=sum(s1.qty)
   from (select title_id, qty=sum(qty) from sales group by title_id) s1,
        (select title_id, qty=sum(qty) from sales group by title_id) s2
   where s1.qty >= s2.qty
   group by s1.title_id
   order by rank
결과:
   Rank    Title_Id    Qty
   ----    --------    ----
   1       MC2222        10
   4       BU1032        60
   4       BU7832        60
   4       PS3333        60
   7       PS1372       140
   7       TC4203       140
   7       TC7777       140
   10      BU1111       250
   10      PS2106       250
   10      PS7777       250
   11      PC1035       330
   12      BU2075       420
   14      MC3021       560
   14      TC3218       560
   15      PC8888       750
   16      PS2091      1728

   (16 row(s) affected)
				

위로 가기

장점

뷰와 결과 서식에도 이러한 쿼리를 사용할 수 있습니다.
순위가 낮은 데이터일수록 더 오른쪽으로 들여 쓸 수 있습니다.
예제 1:
   CREATE VIEW v_pub_rank
   AS
   select rank=count(*), s1.title_id, qty=sum(s1.qty)
   from (select title_id, qty=sum(qty) from sales group by title_id) s1,
        (select title_id, qty=sum(qty) from sales group by title_id) s2
   where s1.qty >= s2.qty
   group by s1.title_id
				
쿼리:
   select  publisher=convert(varchar(20),replicate (' ', power(2,rank)) +
           pub_id +
           replicate(' ', 15-power(2,rank))+': '),
           earnings=qty
   from v_pub_rank
				
결과:
   Publisher       Earnings
   -------------   --------
     0736          : 1,961.85
       0877        : 4,256.20
           1389    : 7,760.85
				
SQL Server 2005에서는 다음 코드를 사용합니다.
CREATE VIEW v_pub_rank
AS
select rank() over (order by sales) as rank,s1.pub_id,s1.sales 
	from (select t.pub_id, sales=sum(s.qty*t.price)
	from sales s, titles t
	where s.title_id=t.title_id
	and t.price is not null
	group by t.pub_id) as s1
GO

select  publisher=convert(varchar(20),replicate (' ', power(2,rank)) +
	pub_id + replicate(' ', 15-power(2,rank))+': '),
	earnings=sales
from v_pub_rank order by rank
GO
결과:
publisher            earnings
-------------------- ---------------------
  0736             : 1961.85
    0877           : 2128.10
        1389       : 2586.95

(3 row(s) affected)

예제 2:
   CREATE VIEW v_title_rank
   AS
   select rank=count(*), s1.title_id, qty=sum(s1.qty)
   from (select title_id, qty=sum(qty) from sales group by title_id) s1,
        (select title_id, qty=sum(qty) from sales group by title_id) s2
   where s1.qty >= s2.qty
   group by s1.title_id
				
쿼리:
   select  Book=convert(varchar(45),replicate (' ', 2*rank) +
           title_id +
           replicate(' ', 35-2*rank)+': '),
           qty
   from v_title_rank
   order by rank
				
결과:
   Book                                          Qty
   -------------------------------------------   ----
     MC2222                                 :      10
           BU1032                           :      60
           BU7832                           :      60
           PS3333                           :      60
                 PS1372                     :     140
                 TC4203                     :     140
                 TC7777                     :     140
                       BU1111               :     250
                       PS2106               :     250
                       PS7777               :     250
                         PC1035             :     330
                           BU2075           :     420
                               MC3021       :     560
                               TC3218       :     560
                                 PC8888     :     750
                                   PS2091   :    1728

   (16 row(s) affected)
				
SQL Server 2005에서는 다음 코드를 사용합니다.
CREATE VIEW v_title_rank
AS
select rank() over (order by qty) as rank, s1.title_id,s1.qty
from (select title_id, qty=sum(qty) from sales group by title_id) as s1
GO

select Book=convert(varchar(45),replicate (' ', 2*rank) +
title_id + replicate(' ', 35-2*rank)+': '), qty
from v_title_rank
order by rank
GO
결과:
Book                                          qty
--------------------------------------------- -----------
  MC2222                                 :    10
    BU1032                               :    15
    BU7832                               :    15
    PS3333                               :    15
          TC4203                         :    20
          TC7777                         :    20
          PS1372                         :    20
                BU1111                   :    25
                PS7777                   :    25
                PS2106                   :    25
                      PC1035             :    30
                        BU2075           :    35
                          MC3021         :    40
                          TC3218         :    40
                              PC8888     :    50
                                PS2091   :    108

(16 row(s) affected)

 




Microsoft 제품 관련 기술 전문가들과 온라인으로 정보를 교환하시려면 Microsoft 뉴스 그룹 (http://support.microsoft.com/newsgroups/default.aspx)에 참여하시기 바랍니다.

위로 가기


본 문서의 정보는 다음의 제품에 적용됩니다.
Microsoft SQL Server 2000 Standard Edition
Microsoft SQL Server 4.21a Standard Edition
Microsoft SQL Server 6.0 Standard Edition
Microsoft SQL Server 6.5 Standard Edition
Microsoft SQL Server 7.0 Standard Edition
Microsoft SQL Server 2005 Standard Edition
Microsoft SQL Server 2005 Developer Edition
Microsoft SQL 2005 Server Enterprise
Microsoft SQL Server 2005 Express Edition
Microsoft SQL 2005 Server Workgroup

위로 가기

키워드:
kbhowtomaster KB186133

위로 가기

AND

출처 : http://www.microsoft.com/korea/technet/columns/outside/net_magazine_04.asp


뷰에 있어서의 백도어
일부 백도어는 사용하면 위험할 수 있으므로 먼저 내부를 살펴볼 필요가 있다


Itzik Ben-Gan

백도어란 애플리케이션이 원래는 지원하려고 의도하지 않았던 것들을 가능하게 하는 문서화되지 않은 기능들을 말한다. 이 기사에서 SQL 서버 뷰에 있어서의 세가지 백도어에 대해 설명하면서 필자의 T-SQL 백도어 연재기사를 마무리하고자 한다. 이번 기사에서 설명할 세가지 뷰의 백도어는 사용자가 INFORMATION_SCHEMA 뷰를 생성하는 방법, 업데이트 가능한 정렬된 뷰를 생성하는 방법, 그리고 뷰에 있어서의 업데이트 제약사항을 피해가는 방법이다. 세 가지 백도어 외에, 문서화되지 않은 기능들 중에서 SQL 서버가 지원한다면 T-SQL 개발자들이 유용하게 사용할 수 있었을 법한 몇 가지 기능들에 대해서도 다룰 것이다.

INFORMATION_SCHEMA 뷰

INFORMATION_SCHEMA 뷰는 데이터베이스와 데이터베이스 내의 사용자 오브젝트들에 대한 메타데이터 정보를 반환하는 뷰이다. INFORMATION_SCHEMA 뷰는 ANSI에서 정의한 표준 포맷으로 정보를 반환하며 ANSI 포맷으로 결과를 반환하는 시스템 테이블 쿼리를 포함한다. INFORMATION_SCHEMA 뷰는 특수 프로시저가 동작하는 것과 유사하게 동작한다. Master 데이터베이스에서만 만들어 두면 모든 다른 데이터베이스에서 사용이 가능하며 수행 시점에 참조하고 있는 데이터베이스의 컨텍스트 내의 시스템 테이블 정보를 반환한다. 예를 들어 Northwind 데이터베이스에서 INFORMATION_SCHEMA.TABLES에 대하여 SELECT 문을 수행하면 Northwind 데이터베이스에 있는 테이블과 뷰의 목록이 반환된다. 이런 기능이 유용함에도 불구하고 불행히도 SQL 서버는 사용자가 INFORMATION_SHCEMA 뷰와 유사하게 동작하는 뷰를 생성하는 것을 지원하지 않는다. SQL 서버를 사용하다 보면 시스템 테이블을 조회하여 SQL 서버가 제공하는 ANSI INFORMATION_SHCEMA 뷰로는 얻을 수 없는 메타데이터 정보를 반환하는 자신만의 뷰를 직접 생성할 필요가 있는 경우가 발생한다. 예를 들어 인덱스에 관한 정보를 반환하는 뷰를 생성하기를 원할 수 있을 것이다. 공식적으로 지원하지는 않지만 T-SQL 백도어를 활용하면 사용자가 자신이 원하는 정보를 반환하는 INFORMATION_SHCEMA 뷰를 생성하는 것이 가능하다.

"allow updates" SQL Server Configuration 옵션을 활성화하면 사용자가 master 데이터베이스에서 INFORMATION_SHCEMA 뷰를 생성할 수 있다.

예를 들어 INFORMATION_SHCEMA.USERTABLES 라는 이름으로 뷰를 생성해 보자. 이 뷰는 데이터베이스에 있는 모든 테이블의 이름과 소유자를 반환(뷰는 제외)하는 뷰이며 [리스트 1]의 코드를 실행하면 이 뷰가 만들어진다.

생성한 뷰를 테스트하려면 Northwind와 Pubs에서 각각 한 번씩 뷰를 쿼리하는 다음 코드를 실행해 보면 된다.

USE Northwind
SELECT * FROM INFORMATION_SCHEMA.USERTABLES
USE pubs
SELECT * FROM INFORMATION_SCHEMA.USERTABLES

첫 번째 쿼리는 Northwind의 사용자 테이블 목록을 반환하고 두 번째 쿼리는 Pubs의 사용자 테이블 목록을 반환한다.

keycol		datacol
------		-------
1		b
2		c	
3		a

그림1. VSortedT1TOP쿼리 화면

정렬된 뷰

ANSI에 의하면 뷰를 정의할 때 SELECT 문에 ORDER BY 절을 지정하는 것이 허용되지 않는다. 이러한 제약 사항은 뷰는 테이블을 대표하는 것이고 테이블에 있는 행들에 대해서는 사전에 정의된 정렬 순서가 없다는 사상에 기초한다. ORDER BY 절이 있는 SELECT 쿼리는 ORDER BY 절이 없는 SELECT 쿼리가 반환하는 것처럼 테이블을 결과로 반환하지 않고 커서를 반환한다. ANSI 규정에 의하면 뷰의 행을 정렬하기를 원한다면 뷰의 정의를 수정하지 말고 뷰를 SELECT 하는 문장에 ORDER BY 절을 명시하도록 되어 있다.

그러나 필자는 종종 Public SQL 서버 포럼 (news://msnews.microsoft.com/microsoft.public.sqlserver.programming 와 같은)에서 정렬된 뷰를 생성하는 방법에 대한 질문을 받는다. 필자가 비록 이런 정렬에 있어서의 제약사항을 피할 수 있는 백도어를 설명하고 있기는 하지만 백도어의 사용은 가급적 피할 것을 권고한다. ORDER BY 절을 지정한 뷰에서 ORDER BY 절을 제거하고 뷰를 액세스하는 쿼리문에 ORDER BY 절을 사용할 것을 권고한다.

정렬된 뷰를 생성할 수 있도록 해 주는 첫 번째 백도어는 SELECT 절에 TOP 100 PERCENT 를 사용하는 것이다. 이 백도어는 SQL 서버에서는 TOP 절을 명시하면 뷰에서도 ORDER BY 절을 지정할 수 있다는 점을 활용한 것이다. ORDER BY 절은 TOP에 대하여 논리적인 의미를 가지기 때문이다. 그러나 TOP 100 PERCENT를 명시하면 요청한 순서대로 정렬된 모든 행들을 반환하기 때문에 TOP 절은 아무런 의미가 없다.

[리스트 2]의 스크립트를 실행하면 이 백도어를 테스트할 수 있다. [리스트 2]의 스크립트는 keycol 컬럼과 datacol 칼럼을 가지는 T1 테이블을 생성하고, 3개 행을 Insert 한 후, VSortedT1TOP 뷰를 생성한다. VSortedT1TOP 뷰는 T1 의 행들을 keycol 순으로 정렬하여 반환한다. 이제, VSortedT1TOP 뷰에 대하여 SELECT * 쿼리를 실행해 보자. [그림 1]에 keycol 순으로 정렬된 세 개의 행이 있다.

이 기법은 ANSI 호환이 아니라는 점 외에도 부정적인 면들을 가진다. 만일 뷰에 대한 쿼리에서 ORDER BY 절을 사용한다면 SQL 서버는 데이터를 두 번 정렬하는 것이 되며 이는 심각한 성능 저하를 유발할 수 있다. 이런 동작 원리를 확인하려면 [리스트 3] 의 코드를 실행해 보면 된다. SHOWPLAN_TEXT 옵션을 설정하고 T1 과 VSortedT1TOP을 쿼리(keycol 컬럼에 대하여 역순으로 정렬)한 다음에 SHOWPLAN_TEXT 옵션을 비활성화한다.

[그림 2]에 T1 테이블에 대한 쿼리의 실행 계획과 VSortedT1TOP 뷰에 대한 쿼리의 실행 계획이 있다. T1 테이블을 직접 쿼리하면 정렬된 결과를 반환하기 위하여 옵티마이저가 keycol 칼럼의 클러스터드 인덱스를 사용하여 정렬된 스캔을 수행한다. 뷰에서 ORDER BY 절을 사용하지 않고, 뷰를 참조하는 쿼리에서 ORDER BY 절을 사용했다면 동일한 실행 계획을 얻을 것이다. 이와 반대로 VSortedT1TOP 뷰를 쿼리하면 우선 T1 에 대해 클러스터드 인덱스 스캔을 수행하는 실행 계획이 생성되고 그 다음에 뷰에 대한 쿼리에서 명시된 정렬 요청에 따라 결과를 재정렬한다. 다시 말하면 SQL 서버는 뷰 안에서 요청된 ORDER BY와 뷰를 참조하는 쿼리에서 요청된 ORDER BY를 별개로 처리하기 때문에 별도의 정렬비용이 발생하게 된다.

A
StmtText	
--------------------------------------------------------
  |--Clustered Index Scan(OBJECT:
  ([tempdb].[dbo].[T1].[PK_T1_
  12731CD]), ORDERED BACKWARD)


B
StmtText
--------------------------------------------------------
  |--Sort(ORDER BY:([T1].[keycol] DESC))
     |--Clustered Index Scan(OBJECT:
     ([tempdb].[dbo].[T1].[PK_T1_
     1273C1CD]), ORDERED FORWARD)
그림2. T1 및 VSortedT1TOP에 대한 정렬된 쿼리의 실행 결과

이 백도어를 사용하는 경우에 발생할 수 있는 또 다른 단점은 TOP 을 사용하여 뷰를 생성하면 뷰에 대하여 업데이트를 수행할 수 없다는 점이다. 다음의 UPDATE 쿼리를 실행해 보면 UPDATE를 실행할 수 없다는 오류 메시지가 반환된다.

UPDATE VSortedT1TOP
SET datacol = 'bb'
WHERE keycol = 2

Microsoft Certified Trainer (MCT)인 Zoltan Kovacs가 업데이트 가능한 정렬된 뷰를 생성할 수있도록 해 주는 백도어를 발견하였다. 이 기법은 OPENQUERY() 함수내의 SELECT 쿼리에 ORDER BY 절을 지정하는 것이다. OPENQUERY() 를 호출하여 자기 자신 서버에 대하여 쿼리를 실행하기 위해서는 우선 data access 서버 옵션을 설정하는 것이 필요하다.

EXEC sp_serveroption
  [], 
  'data access', true

그 다음에 다음 코드를 실행하여 뷰를 생성한다.

CREATE VIEW VSortedT1OPENQUERY AS
SELECT * FROM 
   OPENQUERY([], 
   N'SELECT * FROM tempdb..T1 
   ORDER BY keycol') 
   AS T

이 뷰를 쿼리하면 정렬된 결과를 얻으면서 업데이트도 가능하게 된다. 다음의 UPDATE 문을 실행하여 keycol 컬럼의 값이 2 인 행에 대하여 datacol 컬럼의 값을 'bb' 로 변경해 보자.

UPDATE VSortedT1OPENQUERY
SET datacol = 'bb'
WHERE keycol = 2

이 쿼리는 성공적으로 실행된다. 이 기법의 단점은 ANSI 호환이 아니라는 점 외에 로컬 쿼리에 비해 더 많은 비용이 드는 분산 쿼리를 사용한다는 점이다.

VIEW_METADATA

필자가 마지막으로 설명하는 백도어를 사용하면 그래픽 툴에서 뷰를 조작하는 경우에 뷰가 예상치 못한 방식으로 동작한다. 먼저 겉으로 보기에는 관련이 없어 보이는 뷰의 세 가지 특징들에 대하여 설명한 다음에 그 특징들의 연관성을 살펴 보도록 하겠다.

첫 번째 특징은 보안 매카니즘으로서 뷰의 활용이다. 뷰의 소유자와 테이블의 소유자가 동일한 경우에는 뷰의 베이스 테이블에 대한 액세스 권한이 없는 사용자에게 뷰에 대하여 select, insert, update, delete를 수행할 수 있는 권한을 부여할 수 있다.

exec sp_executesql N'UPDATE
  "tempdb".."T1" SET "datacol"=@P1
  WHERE "keycol"=@P2 AND
  "datacol"=@P3', N'@P1
  varchar(10),@P2 int,@P3
  varchar(10)', 'e', 1, 'a'
그림3. 엔터프라이즈 관리자가 실행하고 프로파일 추적에서 캡쳐된 코드

두 번째 특징은 다중 테이블을 액세스하는 뷰의 수정에 관한 부분이다. 뷰는 테이블처럼 동작하고 테이블과 유사하게 보이는 부분이 많지만 ANSI에서는 몇 가지 차이점들을 규정하고 있고 SQL 서버에서도 그와 같이 구현되어 있다. 여러 테이블을 조인한 뷰의 경우에는 뷰에 대한 하나의 UPDATE 문이나 하나의 INSERT 문에서는 오직 하나의 베이스 테이블만 수정할 수 있다.

세 번째 특징은 뷰를 통하여(베이스 테이블을 직접 수정하지 않고) 뷰의 쿼리 필터링 조건과 상충되는 수정을 하는 부분이다. 그런 수정 작업이 실행되면 입력되거나 수정된 행이 뷰에서 사라진 것처럼 보인다. datacol LIKE 'a%'라는 조건을 사용하여 앞에서 사용했던 T1 테이블의 모든 행을 조회하는 뷰를 생성했다고 가정하자. 뷰를 통하여 datacol 에 'd' 라는 값을 입력하면 정상적으로 수행된다. 그러나 뷰를 쿼리하면 뷰의 쿼리 필터링 조건을 충족시키지 않기 때문에 새로 입력된 행은 반환되지 않는다. 뷰의 쿼리 필터링 조건과 상충되는 수정이 가능하지 않게 하려면 뷰를 생성할 때 WITH CHECK OPTION 을 추가하면 된다. 다음 코드를 실행하면 CHECK 옵션을 명시한 VT1OnlyA 뷰가 만들어진다.

CREATE VIEW VT1OnlyA AS
SELECT * FROM T1
WHERE datacol LIKE 'a%'
WITH CHECK OPTION

이 뷰는 datacol 칼럼의 값이 'a' 로 시작하는 모든 행들을 조회하고 있다. 다음의 INSERT 문은 뷰의 필터링 조건과 상충되지 않는 행을 추가하기 때문에 성공적으로 실행된다.

INSERT INTO VT1OnlyA    VALUES(4, 'aa')

뷰를 쿼리하면 keycol이 1이고 datacol이 'a' 인 1개 행과 keycol이 4이고 datacol 'aa' 인 1개 행이 반환된다. 이제 뷰의 쿼리 필터링 조건과 상충되는 INSERT, UPDATE 문을 실행해 보자.

INSERT INTO VT1OnlyA 
  VALUES (5, 'd')
UPDATE VT1OnlyA
SET datacol = 'e'
WHERE keycol = 1

두 쿼리 모두 쿼리문이 명시된 CHECK 옵션과 상충한다는 오류를 반환한다.

이제 백도어를 활용해 보자. 엔터프라이즈 관리자와 같은 그래픽 인터페이스를 통하여 수정작업을 실행하면 뷰는 앞에서 언급한 세가지 특징에 관하여 의도한 것과 다르게 동작한다. 베이스 테이블이 아닌 뷰를 액세스하는 경우에 엔터프라이즈 관리자를 통해 데이터를 수정하려고 하면 보안 오류가 발생한다. 엔터프라이즈 관리자에서는 뷰를 통하여 한번에 여러 개의 테이블을 변경하는 작업이 성공적으로 수행된다. 뷰의 CHECK 옵션과 상충되는 수정 작업일지라도 엔터프라이즈 관리자에서 뷰를 통해 데이터를 입력 또는 수정하는 경우에는 성공적으로 수행된다.

뷰가 이렇게 다르게 동작하는 이유는 엔터프라이즈 관리자는 클라이언트 데이터베이스 인터페이스를 통하여 SQL 서버로부터 브라우즈 모드 메타데이터 정보를 요청하고 베이스 테이블의 메타데이터 정보를 얻는다. 그런 다음에 엔터프라이즈 관리자가 뷰 대신 베이스 테이블에 대한 쿼리를 작성하기 때문에 이런 차이가 발생하는 것이다.

SQL 서버 프로필러 추적을 활용하면 엔터프라이즈 관리자가 수행하는 쿼리를 캡쳐할 수 있다. 예를 들어 추적을 시작한 상태에서 엔터프라이즈 관리자를 통하여 VT1OnlyA 뷰를 수정하면 [그림 3]과 같은 실행문이 캡쳐된다. 엔터프라이즈 관리자는 뷰 대신 T1 테이블에 대하여 직접 UPDATE 문을 실행한다는 점에 유의하기 바란다. 다행스럽게도 이 문제는 단순히 VIEW_METADATA 옵션을 사용하여 뷰를 수정하면 손쉽게 예방할 수 있다.

ALTER VIEW VT1OnlyA   WITH VIEW_METADATA AS
SELECT * FROM T1   WHERE datacol LIKE 'a%' WITH CHECK OPTION

이 옵션을 사용하면 SQL 서버가 베이스 테이블의 메타데이터 대신 뷰의 메타데이터 정보를 전송한다. 이 방법을 사용하면 백도어를 막을 수 있다.

백도어를 닫아라

백도어를 사용하는 것이 위험하기는 하지만 운영 데이터베이스에서 백도어들에 직면하게 될 때 그런 백도어들을 인지하고 대안을 제시할 수 있는 능력이 필요하다. 필자는 SQL 서버를 사용하는 개발자들이 어떤 SQL 서버 백도어들은 공식적으로 지원되는 기능들만큼 T-SQL 개발자들에게 매우 가치 있는 기능일 수도 있다는 것을 깨닫기를 바란다.

[리스트 1] INFORMATION_SCHEMA.USERTABLES 뷰 생성 스크립트

USE master
GO

EXEC sp_configure 'allow updates', 1
RECONFIGURE WITH OVERRIDE
GO

IF OBJECT_ID('INFORMATION_SCHEMA.USERTABLES') IS NOT NULL
  DROP VIEW INFORMATION_SCHEMA.USERTABLES
GO

CREATE VIEW INFORMATION_SCHEMA.USERTABLES
AS
SELECT USER_NAME(uid) AS owner, name FROM sysobjects
WHERE type = 'u'
GO

EXEC sp_configure 'allow updates', 0
RECONFIGURE WITH OVERRIDE
GO

[리스트 2] T1 테이블과 VSortedT1TOP 뷰 생성 스크립트

USE tempdb

CREATE TABLE T1
(  keycol  int NOT NULL PRIMARY KEY,
   datacol varchar(10) NOT NULL )

SET NOCOUNT ON
INSERT INTO T1 VALUES(1, 'b')
INSERT INTO T1 VALUES(3, 'a')
INSERT INTO T1 VALUES(2, 'c')
GO

CREATE VIEW VSortedT1TOP
AS
SELECT TOP 100 PERCENT *
FROM T1 ORDER BY keycol
GO

[리스트 3] 베이스 테이블로부터 정렬된 데이터를 요청하는 쿼리와 정렬된 뷰로부터 정렬된 데이터를 요청하는 쿼리

SET SHOWPLAN_TEXT ON
GO
SELECT * FROM T1
ORDER BY keycol DESC
SELECT * FROM VSortedT1TOP
ORDER BY keycol DESC
GO
SET SHOWPLAN_TEXT OFF


   최종 수정일 : 2004년 12월 15일

AND

SQL Server 원리 이해

SQL Server 2000의 원리를 이해하면 데이터베이스의 성능을 관리하는 데 도움이 됩니다.

작업자 스레드

SQL Server는 데이터베이스 서버에 전달되는 SQL Server 명령 배치를 서비스하는 데 사용하는 Windows 스레드의 풀을 유지합니다. 들어오는 모든 명령 배치를 서비스하는 데 사용할 수 있는 전체 스레드(SQL Server 용어로는 "작업자 스레드") 수는 sp_configure 옵션인 max worker threads 설정에 따라 결정됩니다. 배치를 능동적으로 전달하는 연결의 수가 max worker threads에 대해 지정된 수보다 크면, 능동적으로 배치를 전달하는 연결들은 작업자 스레드를 공유하게 됩니다. 대부분의 설치에서는 기본값 255를 적용하면 충분할 것입니다. 연결은 대부분 배치가 클라이언트에서 수신되기를 기다리는 데 대부분의 시간을 소비합니다.

작업자 스레드는 SQL Server 버퍼 캐시에서 8KB 데이터 페이지를 작성하는 대부분의 작업을 맡습니다. 작업자 스레드는 최대 성능을 위해 자체 입/출력 동작 일정을 비동기적으로 작성합니다.

지연 기록기

지연 기록기는 버퍼 관리자 내에서 기능을 수행하는 SQL Server 시스템 프로세스입니다. 지연 기록기는 커밋되지 않고 오래된 버퍼(다른 페이지에 재사용되기 전에 먼저 디스크에 기록해야 할 변경 사항이 포함된 버퍼)를 배치로 플러시하여 사용자 프로세서에 사용될 수 있도록 합니다. 이러한 작업은 사용 가능한 빈 버퍼를 만들고 유지 관리하는 데 도움이 됩니다. , 8KB의 빈 데이터 캐시 페이지를 다시 사용할 수 있게 됩니다. 지연 기록기가 8KB 캐시 버퍼를 플러시할 때마다 캐시 페이지의 ID가 초기화되므로 다른 데이터를 빈 버퍼에 쓸 수 있게 됩니다. 지연 기록기는 디스크 입/출력이 적은 기간에 작동함으로써 이 동작이 기타 SQL Server 작업에 미치는 영향을 최소화합니다.

SQL Server는 사용 가능한 버퍼 수준을 자동으로 구성하고 관리합니다. 성능 카운터 SQL Server: Buffer Manager: Lazy Writes/sec은 실제로 디스크에 기록되는 8KB 페이지 수를 나타냅니다. 이 값이 급격히 떨어지는지 보려면 SQL Server: Buffer Manager: Free Pages를 모니터하십시오. 지연 기록기는 SQL Server 작업 전체에서 이 카운터 수준을 최적으로 유지함으로써 사용 가능한 버퍼에 대한 사용자 요구를 만족시킵니다. 시스템 모니터 개체 SQL Server: Buffer Manager: Free Pages의 값이 0에 도달할 경우, 사용자 로드가 지연 기록기에서 제공할 수 있는 크기 이상의 빈 버퍼를 요구할 때가 있었다는 것입니다.

지연 기록기가 빈 버퍼를 안정 수준으로 또는 적어도 0 이상으로 유지하는 데 문제가 있다면, 이는 디스크 하위 시스템이 충분한 디스크 입/출력 성능을 제공하지 못한다는 뜻일 수 있습니다. 빈 버퍼 수준의 감소를 디스크 대기열과 비교하여 사실을 확인하십시오. 해결책은 추가 물리 디스크 드라이브를 데이터베이스 서버 디스크 하위 시스템에 추가하여 디스크 입/출력 처리 능력을 높이는 것입니다.

시스템 모니터에서 Average Disk Queue Length 또는 Current Disk Queue Length 카운터를 보고 현재 디스크 대기열 수준을 모니터하고, 모든 SQL Server 동작과 연관된 각 물리 드라이브마다 디스크 대기열이 2 미만인지 확인합니다. 하드웨어 RAID 컨트롤러와 디스크 어레이를 채택하는 데이터베이스 서버의 경우, 반드시 논리적 또는 물리 디스크 카운터가 보고하는 수를 디스크 관리자가 보고하는 해당 논리 드라이브 문자와 관련된 실제 하드 드라이브 수 또는 물리 하드 드라이브 수로 나누어야 합니다. 이는 Windows SQL Server RAID 컨트롤러에 부착된 물리 하드 드라이브 수를 인식하지 못하기 때문입니다. 시스템 모니터가 보고하는 디스크 대기열 수를 적절히 해석하기 위해서는 RAID 어레이 컨트롤러와 연관된 드라이브의 수를 아는 것이 중요합니다.

자세한 내용은 SQL Server 온라인 문서에서 "freeing and writing buffer pages" "write-ahead transaction log" 문자열을 검색해 보십시오.

검사점

SQL Server 인스턴스는 커밋되지 않은 로그와 데이터 페이지를 주기적으로 디스크에 플러시합니다. 이를 검사점이라고 합니다. 검사점은 SQL Server 인스턴스가 다시 시작될 때 장애 복구에 필요한 시간과 리소스를 줄여줍니다. 검사점 중에 커밋되지 않은 페이지(버퍼 캐시에 놓인 이후 수정된 버퍼 캐시 페이지) SQL Server 데이터 파일에 기록됩니다. 검사점에 디스크로 쓰여진 버퍼에는 여전히 페이지가 들어 있으며 사용자는 디스크에서 다시 읽지 않고 읽거나 업데이트할 수 있습니다. 이것은 지연 기록기에 의해 작성된 빈 버퍼에는 해당되지 않습니다.

검사점 논리는 작업자 스레드와 지연 기록기가 커밋되지 않은 페이지를 기록하는 대부분의 작업을 수행하도록 합니다. 이를 위하여 검사점 논리는 가능한 경우 커밋되지 않은 페이지를 쓰기 전에 여분의 검사점 대기를 시도합니다. 이렇게 하면 작업자 스레드와 지연 기록기가 커밋되지 않은 페이지를 쓰는 데 더 많은 시간을 할애할 수 있습니다. 이와 같이 커밋되지 않은 페이지에 여분의 대기 시간이 발생하는 조건에 대한 내용은 SQL Server 온라인 문서의 "Checkpoints and the Active Portion of the log" 절에 자세히 나와 있습니다. 기억해 두어야 할 사항은 검사점 논리가 보다 긴 시간 동안 SQL Server 디스크 입/출력 작업을 안정시키기 위해 여분의 검사점 대기를 사용한다는 사실입니다.

보다 효율적인 검사점 작업을 위해서는 캐시에서 플러시해야 할 페이지가 많을 때 SQL Server가 플러시할 데이터 페이지를 디스크에 나타난 페이지 순서로 정렬합니다. 이렇게 하면 캐시 플러시 도중 디스크 암의 움직임을 최소화하는 데 도움이 되며, 가능한 경우 순차적 디스크 입/출력의 이점을 이용할 수 있습니다. 검사점 프로세스는 또한 8KB 디스크 입/출력 요청을 비동기적으로 디스크 하위 시스템에 전달합니다. 이로 인해 SQL Server는 필요한 디스크 입/출력 요청을 더 빨리 전달할 수 있습니다. 이는 검사점 프로세스가 데이터가 실제로 디스크에 기록되었음을 디스크 하위 시스템이 보고할 때까지 기다리지 않기 때문입니다.

SQL Server 데이터 파일과 연관된 하드 드라이브의 디스크 대기열을 살펴보면서 SQL Server가 디스크가 처리할 수 있는 것보다 더 많은 디스크 입/출력 요청을 보내고 있지 않는지 확인하고, 이러한 경우가 발생하면 디스크 하위 시스템에 디스크 입/출력 용량을 추가하여 하위 시스템이 로드를 처리할 수 있도록 해야 합니다.

로그 관리자

다른 모든 주요 RDBMS 제품과 마찬가지로 SQL Server는 데이터베이스에서 수행된 모든 쓰기 작업(삽입, 업데이트 및 삭제) SQL Server의 온라인 상태를 중단시키는 요인(정전, 디스크 드라이브 결함, 데이터 센터 화재 등)으로 인해 손실되지 않도록 보장합니다. SQL Server 로깅 프로세스를 통해 복구 가능 상태를 유지합니다. 로그 관리자가 디스크 하위 시스템으로부터 해당 트랜잭션과 연관된 모든 데이터 변경 사항이 관련 로그 파일에 성공적으로 기록되었다는 신호를 받아야 암시적 트랜잭션(단일 SQL 쿼리) 또는 명시적 트랜잭션(BEGIN TRAN/COMMIT 또는 ROLLBACK 명령 시퀀스를 발행하는 정의된 트랜잭션)이 완료될 수 있습니다. 이 규칙에 따라 SQL Server가 어떤 이유로든 이상 종료되고 데이터 캐시에 기록된 트랜잭션이 검사점과 지연 기록기에 의해 데이터 파일로 아직 플러시되지 않은 경우 SQL Server를 다시 가동할 때 반드시 트랜잭션 로그가 읽혀지고 다시 적용될 수 있습니다. 서버 중단 후에 트랜잭션 로그를 읽고 SQL Server 데이터에 트랜잭션을 적용하는 것을 복구라고 합니다.

SQL Server는 각 트랜잭션이 완료되면서 디스크 하위 시스템이 SQL Server 로그 파일에 대한 입/출력을 완료할 때까지 기다려야 하므로, SQL Server 로그 파일을 포함하는 디스크의 용량은 예상 트랜잭션 로드에 맞는 디스크 입/출력을 처리 능력을 갖고 있어야 합니다.

SQL Server 로그 파일과 관련된 디스크 대기를 살펴보는 방법은 SQL Server 데이터베이스 파일과 다릅니다. 시스템 모니터 카운터 SQL Server: Databases : Log Flush Waits Times SQL Server: Databases : Log Flush Waits/sec를 사용하여 디스크 하위 시스템에서 완료 대기 중인 로그 작성기 요청이 있는지 확인하십시오.

캐시 컨트롤러는 최고 성능을 제공하지만 로그 파일을 포함하는 디스크에는 사용할 수 없습니다. , 컨트롤러는 전원 장애가 발생하는 경우에도 마지막에 신뢰할 수 있는 데이터만 디스크에 기록할 것을 보증하는 경우는 예외입니다. 캐시 컨트롤러에 대한 자세한 내용은 뒤에 나오는 "하드웨어 RAID 컨트롤러에 내장된 캐시의 효과" 절을 참조하십시오.

미리 읽기 관리자

SQL Server 2000은 테이블 스캔 등의 작업에 많은 순차적 읽기에 대한 자동 관리를 제공합니다. 미리 읽기 관리는 완전히 자동으로 구성 및 조정되며 SQL Server 쿼리 프로세서의 작업과 밀접하게 통합됩니다. 미리 읽기 관리는 큰 테이블 스캔, 큰 인덱스 범위 스캔, 클러스터링된 인덱스 및 클러스터링되지 않은 인덱스 바이너리 트리 탐색 및 기타 상황에 사용됩니다. 이것은 미리 읽기가 8KB /출력에 비해 더 많은 디스크 처리량을 제공하는 64KB /출력으로 발생하기 때문입니다. 아주 많은 양의 데이터를 검색해야 하는 경우, SQL Server는 미리 읽기를 사용하여 처리율을 극대화합니다.

SQL Server는 미리 읽기 관리를 지원하는 간단하고 효율적인 IAM(Index Allocation Map) 저장소 구조를 사용합니다. IAM은 일정 범위의 여러 위치를 기록하는 SQL Server 메커니즘입니다. 각각의 64KB 범위에 데이터나 인덱스 정보가 8페이지씩 들어 있습니다. IAM 페이지의 크기는 8KB이며, 이 페이지는 어느 범위에 필요한 데이터가 들어 있는지에 대한 정보(비트맵 형식)를 포함합니다. IAM 페이지의 압축 특성 때문에 빠르게 읽혀지며, 보다 정기적으로 사용되는 IAM 페이지를 버퍼 캐시에 유지 관리할 수 있습니다.

미리 읽기 관리는 쿼리 프로세서의 쿼리 정보를 IAM 페이지에서 읽어야 할 모든 범위의 위치에 대한 정보와 결합하여 여러 순차적 읽기 요청을 구성합니다. 순차적 64KB 디스크 읽기는 극히 양호한 디스크 입/출력 성능을 제공합니다. SQL Server: Buffer Manager: Read-Ahead Pages/sec 성능 카운터는 미리 읽기 관리의 효율과 효과에 대한 정보를 제공합니다.

SQL Server 2000 Enterprise Edition은 미리 읽기 페이지의 최대 수를 존재하는 메모리 크기를 기준으로 동적으로 조정합니다. 다른 모든 SQL Server 2000 버전에서는 값이 고정됩니다. SQL Server 2000 Enterprise Edition의 또 하나의 확장 기능은 흔히 말하는 회전목마(merry-go-round) 스캔으로, 여러 작업이 전체 테이블 스캔을 공유할 수 있도록 하는 기능입니다. SQL 문의 실행 계획이 테이블의 데이터 페이지 스캔을 요청하고 관계형 데이터베이스가 해당 테이블이 이미 다른 실행 계획에 의해 스캔되고 있음을 발견할 경우, 데이터베이스 엔진은 두번째 스캔의 현재 위치에서 스캔을 첫번째 스캔에 조인합니다. 데이터베이스 엔진은 각 페이지를 읽고 페이지의 행을 두 실행 계획 모두로 전달합니다. 이러한 작업은 테이블의 끝에 도달할 때까지 계속됩니다. 테이블의 끝에 도달하면, 첫번째 실행 계획이 완전한 스캔 결과를 갖게 되지만 두번째 실행 계획은 진행 중이던 스캔과 조인된 시점 이전에 발생한 데이터 페이지 검색을 계속해야 합니다. 두번째 실행 계획에 대한 검색은 테이블의 첫 데이터 페이지로 되돌아가서 첫번째 스캔과 조인된 시점으로 계속 진행됩니다. 나머지 추가 스캔도 이와 같은 방법으로 결합할 수 있습니다. 데이터베이스 엔진은 모든 스캔을 완료할 때까지 데이터 페이지 순환을 계속합니다.

미리 읽기 관리에 있어 한 가지 주의할 사항은 너무 많은 미리 읽기는 전반적인 성능을 떨어뜨릴 수 있다는 점입니다. 그 이유는 다른 목적으로 사용될 수도 있는 입/출력 및 CPU를 사용하여 캐시가 불필요한 페이지로 가득찰 수 있기 때문입니다. 솔루션은 최소한의 페이지가 버퍼 캐시로 들어오도록 모든 SQL 쿼리를 조정하는 일반적인 성능 조정입니다. 인덱스가 올바르며 그러한 인덱스를 사용하고 있는지 확인하는 것도 여기에 포함됩니다. 효율적인 범위 스캔을 위해 클러스터링된 인덱스를 사용하고 단일 행이나 그보다 더 작은 행 집합의 위치를 신속하게 찾는 데 도움이 되도록 클러스터링되지 않은 인덱스를 정의합니다. 예를 들어, 테이블에 한 개의 인덱스만 있도록 하거나 해당 인덱스가 단일 행이나 적은 행 집합을 반입하는 것이 목적인 경우, 인덱스를 클러스터링해야 합니다. 클러스터링된 인덱스는 보통 클러스터링되지 않은 인덱스보다 속도가 빠릅니다.

성능에 관한 기타 설명

별모양과 눈송이 스키마를 사용하여 데이터베이스 설계

데이터 웨어하우스는 차원 모델링을 사용하여 분석 용도로 데이터를 구성합니다. 차원 모델링은 별모양과 눈송이 스키마를 만듭니다. 이러한 스키마는 데이터 웨어하우징에서 자주 수행되는 대량의 데이터 읽기 작업에서 성능 효율성을 높여줍니다. 부피가 큰 데이터(종종 수십억 개의 행으로 구성됨)는 길이가 매우 짧은 행들로 구성된 팩트 테이블에 저장됩니다. 이러한 테이블은 저장소 요구와 쿼리 시간을 최소화합니다. 비즈니스 실체의 속성을 비표준 상태로 차원 테이블에 넣어 데이터를 검색할 때 테이블 조인 수를 최소화합니다.

데이터 웨어하우스용 데이터베이스 설계에 대한 자세한 내용은 Microsoft SQL Server 2000 Resource Kit "데이터 웨어하우스 설계 고려 사항"을 참조하십시오.

가능한 한 피하는 것이 좋은 SQL

SQL 쿼리에서 부등 연산자를 사용하면 데이터베이스에서 부등 조건을 평가하기 위해 테이블 스캔을 사용하게 됩니다. 아주 큰 테이블에 대해 이러한 쿼리가 자주 수행되면 입/출력 횟수가 많아집니다. "NOT" 연산자(!=, <>, !<, !>)를 포함하는 WHERE (: WHERE != some_value)은 많은 입/출력을 생성하지 않습니다.

이러한 형태의 쿼리를 실행해야 하는 경우에는 쿼리 구조를 바꾸어 NOT 키워드를 없애도록 하십시오. 예를 들면 다음과 같습니다.

다음 쿼리에서

select * from tableA where col1 != "value"

다음 쿼리로

select * from tableA where col1 < "value" and col1 > "value"

 

행 세트 크기 및 통신 오버헤드 줄이기

SQL로 작업하는 데이터베이스 프로그래머는 구축할 결과 세트를 고려하는 데 ADO(Microsoft ActiveX® Data Objects), RDO(Remote Data Objects) DAO(Data Access Objects) 데이터베이스 API와 같이 손쉬운 인터페이스를 사용합니다. ADO/RDO/DAO는 프로그래머들이 SQL 프로그램 작성 경험이 많지 않아도 풍부한 SQL 행 집합 기능을 만들 수 있는 뛰어난 데이터베이스 개발 인터페이스입니다. 하지만 추가 비용이 듭니다. 프로그래머의 응용 프로그램이 클라이언트로 반환하는 데이터 양을 신중하게 고려하고 인덱스가 놓일 SQL Server에 대한 추적을 유지하고 SQL Server 데이터의 정렬 방법을 고려하여 성능 문제를 피할 수 있습니다. SQL 프로필러, 인덱스 튜닝 마법사 및 그래픽 방식 실행 계획은 이러한 문제 쿼리를 정확히 찾아내 수정하는 데 아주 유용한 도구입니다. 커서 논리를 사용할 때, 수행하려는 처리 유형에 적합한 커서를 선택하십시오. 다양한 유형의 커서가 각기 다른 가격으로 제공됩니다. 반드시 수행할 작업의 종류(읽기 전용, 전송 처리만 등)를 파악한 다음에 해당되는 커서 유형을 선택하십시오. 선택 목록에서 반환할 필요가 없는 열을 제거하거나 오직 필요한 행만을 반환함으로써 기회가 있을 때마다 반환되는 결과 집합 크기를 줄여야 합니다. 이렇게 하면 입/출력과 CPU 소모량을 줄이는 데 도움이 됩니다.

여러 명령문 사용 데이터베이스에서 처리를 수행하여 클라이언트와 데이터베이스 서버 사이의 불필요한 네트워크 통신과 결과 세트의 크기를 줄일 수 있습니다. SQL Server에서 하나의 Transact-SQL 문만으로는 완료할 수 없는 처리를 수행하기 위해 다음과 같은 방법으로 여러 Transact-SQL 문을 그룹으로 묶을 수 있습니다.

그룹화 방법

설명

일괄 처리

일괄 처리는 응용 프로그램에서 서버로 하나의 단위로 보낸 하나 이상의 Transact-SQL 문 그룹입니다. SQL Server는 각 일괄 처리를 하나의 실행 가능한 단위로서 실행합니다.

저장 프로시저

저장 프로시저는 서버에 사전 정의되어 컴파일된 Transact-SQL 문 그룹입니다. 저장 프로시저는 매개 변수를 받아서 결과 세트, 반환 코드 및 출력 매개 변수를 호출하는 응용 프로그램으로 반환할 수 있습니다.

트리거

트리거는 저장 프로시저의 특별한 종류로, 직접 응용 프로그램에 의해 호출되지 않습니다. 그 대신 사용자가 테이블에 지정된 수정 작업(INSERT, UPDATE 또는 DELETE)을 수행할 때마다 실행됩니다.

스크립트

스크립트는 파일에 저장된 일련의 Transact-SQL 문입니다. 파일은 osql 유틸리티 또는 SQL 쿼리 분석기에 대한 입력으로 사용할 수 있습니다. 그러면 유틸리티가 파일에 저장된 일련의 Transact-SQL 문을 실행합니다.

다음과 같은 SQL Server 기능을 사용하여 한 번에 여러 개의 Transact-SQL 문을 사용하는 것을 제어할 수 있습니다.

기능

설명

Control-of-flow

조건 논리를 포함시킬 수 있습니다. 예를 들어, 국가가 캐나다이면 일련의 Transact-SQL 문을 실행합니다. 그리고 국가가 영국일 때는 다른 일련의 Transact-SQL 문을 실행합니다.

변수

나중에 Transact-SQL 문의 입력으로 사용할 데이터를 저장할 수 있습니다. 예를 들어, 쿼리를 실행할 때마다 WHERE 절에 다른 데이터 값이 지정되어야 하는 쿼리를 코딩할 수 있습니다. WHERE 절에 변수를 사용하는 쿼리를 작성한 다음, 적절한 데이터로 변수를 채우는 논리를 코딩할 수 있습니다. 저장 프로시저의 매개 변수는 특별한 변수 클래스입니다.

오류 처리

SQL Server가 문제점에 응답하는 방법을 사용자 지정하도록 합니다. 오류가 발생할 때 취할 적절한 조치를 지정하거나 일반 SQL Server 오류보다 더 많은 정보를 제공하는 사용자 지정 오류 메시지를 표시할 수 있습니다.

실행 계획 재사용

SQL Server가 이전의 쿼리에서 기존의 실행 계획을 활용할 수 있을 때 성능이 향상될 수 있습니다. 개발자는 여러 가지 방법으로 SQL Server가 실행 계획을 다시 사용하도록 할 수 있습니다. Transact-SQL 문은 다음과 같은 지침에 따라 작성해야 합니다.

·                     테이블이나 뷰 등의 개체에 대해 완전히 규정된 이름을 사용하십시오.

예를 들면 다음 SELECT 문과 같이 코딩하지 마십시오.

SELECT * FROM Shippers WHERE ShipperID = 3

ODBC를 사용하는 대신, 다음과 같은 SQLBindParameter ODBC 함수를 사용하십시오.

SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = 3

·                     저장 프로시저 매개 변수 값을 지정하거나 검색 조건 조건자에 직접 값을 지정하는 것이 아니라 매개 변수화된 쿼리를 사용하고 매개 변수 값을 제공하십시오. sp_executesql에 매개 변수 대체를 사용하거나 ADO, OLE DB, ODBC DB 라이브러리 API의 매개 변수 바인딩을 사용합니다.

예를 들면 다음 SELECT 문과 같이 코딩하지 마십시오.

SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = 3

ODBC를 사용하는 대신, 한 예로 SQLBindParameter ODBC 함수를 사용하여 매개 변수 표시(?)를 프로그램 변수에 바인드하고 SELECT 문을 다음과 같이 코딩하십시오.

SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = ?

·                     Transact-SQL 스크립트, 저장 프로시저 또는 트리거에서 sp_executesql을 사용하여 SELECT 문을 실행하십시오.

DECLARE @IntVariable INT
DECLARE @SQLString NVARCHAR(500)
DECLARE @ParmDefinition NVARCHAR(500)
/* Build the SQL string. */
SET @SQLString =
N'SELECT * FROM Northwind.dbo.Shippers WHERE ShipperID = @ShipID'
/* Specify the parameter format once. */
SET @ParmDefinition = N'@ShipID int'
/* Execute the string. */
SET @IntVariable = 3
EXECUTE sp_executesql @SQLString, @ParmDefinition,
@ShipID = @IntVariable

sp_executesql 은 별도의 저장 프로시저를 작성 및 유지 관리할 때 오버헤드가 생기는 것을 원치 않는 경우에 좋은 대안입니다.

일괄 처리에 대한 실행 계획 재사용

여러 개의 동시 실행 응용 프로그램이 알고 있는 매개 변수 집합이 지정된 동일한 일괄 처리를 실행할 경우, 응용 프로그램에 의해 호출되는 저장 프로시저로 일괄 처리를 구현하십시오.

ADO, OLE DB 또는 ODBC 응용 프로그램이 동일한 일괄 처리를 여러 번 실행할 경우, PREPARE/EXECUTE 모델을 사용하여 일괄 처리를 실행하십시오. 프로그램 변수에 바인드된 매개 변수 표시를 사용하여 필요한 모든 입력 값을 제공하십시오. 예를 들면 UPDATE VALUES 절에 사용되는 식이나 검색 조건의 조건자에 사용되는 식입니다.

열의 통계 관리

SQL Server에서는 열이 인덱스에 속하지 않더라도 열의 값 분산에 사용할 통계 정보를 작성할 수 있습니다. 쿼리 프로세서는 이 통계 정보를 사용하여 쿼리 평가에 가장 적합한 전략을 결정할 수 있습니다. 인덱스를 만들면, SQL Server는 인덱싱된 열에 값을 분산시키는 작업과 관련된 통계 정보를 자동으로 저장합니다. 인덱싱된 열 외에, AUTO_CREATE_STATISTICS 데이터베이스 옵션이 ON(기본값)으로 설정된 경우에도 SQL Server가 조건자에 사용된 열에 대한 통계를 자동으로 생성합니다. 해당 열이 인덱싱된 열이 아닐 경우에도 마찬가지입니다.

열의 데이터가 변경되면, 인덱스와 열 통계가 더 이상 올바르지 않게 되고 쿼리 최적화 프로그램이 쿼리 처리 방법에 대해 최적의 결정을 내릴 수 없게 됩니다. 주기적으로 SQL Server는 테이블의 데이터 변경에 따른 통계 정보를 자동으로 업데이트합니다. 샘플링은 데이터 페이지에서 무작위로 이루어지며, 통계에 필요한 열에 최소 크기의 클러스터링되지 않은 인덱스 또는 테이블로부터 가져옵니다. 디스크에서 읽어온 데이터 페이지의 모든 행을 사용하여 통계 정보를 업데이트합니다. 통계 정보의 업데이트 주기는 열의 데이터 크기나 인덱스 및 변경 데이터의 크기에 따라 결정됩니다.

예를 들어, 10,000개의 행을 포함하는 테이블에 대한 통계는 1,000개의 인덱스 값이 변경된 후 업데이트되어야 할 수 있습니다. 이 테이블에서 10,000개 중 1,000개는 큰 비율이기 때문입니다. 그러나 천만 개의 인덱스 항목이 포함된 테이블에서 1,000개의 변경되는 인덱스 값은 그리 중요하지 않기 때문에 자동으로 통계가 업데이트되지 않을 수도 있습니다. 그러나 SQL Server는 언제나 최소의 행이 샘플링되도록 유지합니다. , 항상 8MB 미만의 테이블이 완전 스캔되어 통계를 수집하도록 합니다.

참고 쿼리의 실행 계획이 SQL 쿼리 분석기를 통해 그래픽으로 표시될 때, 오래되었거나 누락된 통계에는 경고(빨간색 테이블 이름) 표시가 됩니다. 또한 SQL 프로필러를 사용하는 Missing Column Statistics 이벤트 클래스는 통계가 유실될 때 이를 표시해 줍니다.

통계는 sp_createstats 시스템 저장 프로시저를 사용하여 한 명령문으로 현재 데이터베이스의 모든 사용자 테이블에서 적합한 모든 열에 쉽게 생성될 수 있습니다. 통계에 적합하지 않은 열에는 결정적이지 않은 또는 정확하지 않은 계산된 열, 또는 image, text ntext 테이터 형식의 열이 있습니다.

수동으로 통계를 작성하면 여러 행 밀도(열 조합에 대한 평균 중복 수)를 포함하는 통계를 구할 수 있습니다. 예를 들면 쿼리가 다음과 같은 절을 포함합니다.

WHERE a = 7 and b = 9

두 열(a, b) 모두에 대한 통계를 수동으로 작성하면 SQL Server가 쿼리를 더 잘 평가할 수 있는데, 그 이유는 a b 열 조합에 대한 고유한 값의 평균도 통계에 포함되기 때문입니다. 이렇게 하면 SQL Server에서 테이블 스캔에 의존할 필요 없이 col1에 만들어진 인덱스를 사용할 수 있습니다(이 경우 클러스터링된 인덱스). 열 통계를 작성하는 방법에 대해서는 SQL Server Books Online "CREATE STATISTICS"를 참조하십시오.

추가 정보 찾기

·       SQL Server 온라인 문서는 SQL Server 아키텍처와 데이터베이스 조정에 대한 정보는 물론 명령 구문과 관리에 대한 전체 문서를 제공합니다. SQL Server 온라인 문서는 모든 SQL Server 클라이언트 또는 서버를 설치할 때 SQL Server 설치 미디어에서 설치할 수 있습니다.

·       Microsoft SQL Server에 대한 최신 정보(SQL Server에 대한 기술 백서 포함)를 보려면 다음의 Microsoft SQL Server 웹 사이트를 방문하십시오.

·       http://www.microsoft.com/korea/sql

·       http://www.microsoft.com/korea/technet/sql

·       http://www.microsoft.com/korea/msdn/sqlserver

·       정기적 간행물 형식의 정보를 제공하는 외부 리소스는 http://www.sqlmag.com  에 나와 있습니다. 여기서는 SQL Server의 내부 작업을 개괄적으로 설명하는 많은 최적화 및 조정 힌트, 코드 샘플 및 기타 중요한 정보를 제공합니다. information.

·       Delaney, Kalen & Soukup, Ron. Inside Microsoft SQL Server 2000, Microsoft Press, 2001.

이 책은 이전 버전(Inside Microsoft SQL Server 7.0) SQL Server 2000 정보를 이용하여 업데이트한 것으로, 쉽게 구하기 어려운 SQL Server의 본질적 개념에 대해 중점적으로 설명합니다.

·       Kimball, Ralph. Data Warehouse Lifecycle Toolkit, John Wiley & Sons, 1998.

많은 사람들이 최고의 데이터 웨어하우징 입문서 중 하나로 꼽는 책입니다. 데이터 웨어하우스 데이터베이스 설계에 대한 탁월한 통찰력을 제공하고 차원 모델링 개념에 대해 상세하게 설명하고 있습니다.

·       Celko, Joe. SQL for Smarties. Morgan Kaufmann, 1999.

이 책에는 매우 유용한 정보가 들어 있습니다. 계층 데이터를 표현하고 쿼리하는 것과 같은 공통적인 문제에 대한 해결책이 들어 있습니다. 28장은 SQL 쿼리 최적화에 대한 것입니다.

이 문서에 포함된 정보는 문서를 발행할 때 논의된 문제들에 대한 Microsoft Corporation의 당시 관점을 나타냅니다. Microsoft는 변화하는 시장 환경에 대처해야 하므로 이를 Microsoft 측의 책임으로 해석해서는 안되며 발행일 이후 소개된 어떠한 정보에 대해서도 Microsoft는 그 정확성을 보증하지 않습니다.

이 백서는 정보 제공 목적으로만 제공됩니다. MICROSOFT는 이 문서에서 명시적이든 묵시적이든 막론하고 여하한 보증도 하지 않습니다.

해당 저작권법을 준수하는 것은 사용자의 책임입니다. 저작권의 권리와 별도로, 이 설명서의 어떠한 부분도 Microsoft의 명시적인 서면 승인 없이는 어떠한 형식이나 수단(전기적, 기계적, 복사기에 의한 복사, 디스크 복사 또는 다른 방법)으로 또는 어떠한 목적으로도 복제하거나, 검색 시스템에 저장 또는 도입하거나, 전송할 수 없습니다.

Microsoft는 본 설명서 내용과 관련된 특허권, 상표권, 저작권 또는 기타 지적 소유권을 보유할 수 있습니다. 서면 사용권 계약에 따라 Microsoft에서 귀하에게 명시적으로 권리를 제공하지 않으면, 이 설명서 제공으로는 이러한 특허권, 상표권, 저작권 또는 기타 지적 소유권 등에 대한 어떠한 사용권도 귀하에게 부여되지 않습니다.


AND


SELECT
      A.name            -- 테이블명
,     B.reserved*8 as reserved      -- 할당된 공간
,     B.dpages*8 as dataSize        -- 데이터 공간
,     (B.used-B.dpages)*8 as indexSize   -- 인덱스 공간
,     (B.reserved-B.used)*8 as unused     -- 사용 안하는 여유공간
--,     B.name    -- 인덱스명
--,     B.rows    -- 로우수
FROM sysobjects A WITH(NOLOCK)
     INNER JOIN sysindexes B WITH(NOLOCK) ON A.id=B.id
WHERE A.xtype='U'                          -- 사용자 테이블만 조회
     AND B.name not like '_WA%'        -- 자동생성 통계 제외
     AND B.indid IN (0,1)                    -- 힙 or 클러스터드 인덱스만 조회
ORDER BY A.name
AND

spin lock

SQL Server 2007. 2. 27. 16:37

오늘은 스레드 동기화에 대해서 제 생각(다년간의 삽질)을 정리하려고 합니다.  

한가지 궁금한점이 있습니다. 제 블로그에 하루에 약200명정도 오시는 것 같은데 덧글을 남기거나 의견을 주시는 분은 하루에 1분도 없습니다. 제가 틀리거나 잘 못 알고 있는 것이 있을텐데...

Please do not heasitate any opinion say or write to me^,.^

사설이 길었네요 그럼 시작합니다유^^

[Thread Synchronization Overview]

-
동기화 관련 시스템 콜들은 CPU 자원을 많이 소모하기 때문에 최대한 피하는 것이 좋다.

(CPU
자원을 많이 소모하기 때문에 사용않할 수도 없고 어찌해야 하죠 ㅠ,.)

-
동기화 관련 객체 LOCK/RELEASE 사이의 코드는 최대한 짧게 작성해야 한다.

(
너무 짧게 하면 오히려 동기화가 안되는 경우가 있었습니다. 반대로 코드를 길게 동기화를 하면 성능이 낮아집니다. 양날의 검같습니다. 특히 STL사용할 때는 주의 해야 합니다. 좀더 자세한 내용은 제블로그 에 있습니다.)


[Critical Section]
크리티컬 섹션이 유저 모드에서 동작하기 때문에 일반적으로 뮤텍스보다 빠르다(2 ~ 10)는 것은 널리 알려져 있다. 그러면 왜 빠른지에 대해서 간단하게 살펴 보겠습니당.

1)
스레드가 EnterCriticalSection 함수를 호출한 경우, 유저모드에서 우선적으로 CRITICAL_SECTION 구조체 내부의 변수를 검사하게 된다. 이 값이 off인 경우, 바로 atomic 연산을 통해 이 값을 on으로 바꾸고 다음 작업을 진행하게 된다. 커널모드로 내려가지 않고 유저모드에서 바로 동작을 수행합니다.

2)
크리티컬 섹션이 다른 스레드에 점유되어 있는 경우, 싱글 CPU 상에서는 바로 커널 모드로 넘어가서 동기화 작업이 수행되고, 다중 CPU 상에서는 Spin Count만큼 대기(busy-waiting)하면서, 락이 풀리기를 기다리게 된다.

3)
커널 모드로 넘어가게 되면, 해당 스레드는 세마포어를 이용한 WAIT 상태가 된다.

4)LeaveCriticalSection
함수를 호출하게 되면, CRITICAL_SECTION 구조체 내부의 변수를 off로 바꾸고, 세마포어를 이용한 경우에는 기다리고 있는 스레드에게 통지를 해주게 된다.

5)EnterCriticalSection
한 스레드에서 또 EnterCriticalSection을 호출 하면 내부적으로 Count 변수를 올려서 Deadlock을 막아준다. .EnterCriticalSection을 호출한 횟수만큼 LeaveCriticalSection을 호출해 주어야 한다.

정리해보자면 크리티컬 섹션이 뮤텍스보다 빠른 것은 2가지 경우다.



1)
같은 크리티컬 섹션 객체에 접근하는 스레드의 숫자가 적어서, CRITICAL_SECTION 내부 락 변수만으로 동기화가 이루어질 때.

2)
적당한 스핀 카운트를 통해 WAIT 상태로 가기 전에 락을 획득할 수 있을 때. (SMP 머신의 경우)

, 크리티컬 섹션 관련 스레드가 최대한 커널 모드로 들어가지 않도록 해줘야한다는 말이다.

[Critical Section Spin Count]
위에서도 설명했듯이, SMP 시스템에서 크리티컬 섹션과 관련된 병목을 줄이기 위해서는 적당한 스핀 카운트를 설정 해야 한다. 커널 모드로 들어가기 보다는 유저 모드에서 busy-waiting을 하는 게 나을 수 있기 때문이다. 이를 위한 API 함수가


SetCriticalSectionSpinCount
InitializeCriticalSectionAndSpinCount
(
자세한 내용은 MSDN을 함 살펴 보이소)

Spin Count 값으로서 어느 정도의 값을 주는지는 당연히 시스템/애플리케이션마다 틀리다.
참고로 MS-SQL 에서 스핀카운트 값을 4000으로 설정합니다.(16진수인지 10진수인지 혼동되요-,.)
그리고 스핀카운트는 CPU가 최소 HyperThrading 을 지원하는 곳에서 성능이 향상된다.




[Semaphore Throttles (Thundering Herd(
놀란 양떼)]

하나의 동기화 객체(크리티컬 섹션 또는 뮤텍스)를 기다리는 스레드가 너무 많은 경우, 세마포어를 하나 더 둬서, 해당 동기화 객체를 기다리는 스레드의 숫자를 줄여주는 것이 성능 향상에 도움이 될 수 있다.

(
이 내용은 이해하기 어려울 것같습니다. 저도 처음으로  Efficient C++ 책에서 읽은 내용인데 무심코 넘어갔습니다. 그러나 MultiCPU에서 초강력 슈퍼 울트라 초특급 MultiThreading 프로그램을 개발할때는 꼭 알고 있어야 합니다. 나중에 따로  Efficient C++ 책 내용을 기초로 글을 작성하겠습니다. 기둘리이소^^)


while (TRUE)

{ // Worker loop
   WaitForSingleObject (hThrottleSem, INFINITE);
   WaitForSingleObject (hMutex, INFINITE);
      ... Critical code section ...
   ReleaseMutex (hMutex);
   ReleaseSemaphore (hThrottleSem, 1, NULL);
} // End of worker loop

 

스핀잠금

단기적 잠금이 필요한 경우에 SQL Server는 스핀 잠금(spinlock)으로 구현된 래치를 사용하여 상호 배타적 기능을 얻는다. 스핀 잠금은 순수하게 상호 배타성 기능을 위해서만 사용되고, 결코 사용자 데이터를 잠그지 않는다. 스핀 잠금은 래치보다 훨씬 가볍다.(그리고 래치는 데이터 페이지와 인덱스 잎 페이지에 사용되는 오나전한 잠금보다 더 가볍다.) 스핀 잠금은 SQL Server에만 있는 기능이고, 이 프로세서 형식을 구현하기 위해 프로세서 관련 어셈블리 언어가 사용된다. 스핀 잠금은 각 프로세서 형식과 관련된 몇 라인의 어셈블리 언어로 구현된다. 스핀 잠금을 요청한 개체는 이 잠금이 즉시 사용 가능하게 될 때까지 반복적으로 자신의 요청을 전달한다. 스핀 잠금은 일상적으로 자주 사용되지 않는 리소스에 대해 SQL Server내에서 종종 mutex로 사용된다. 리소스가 자주 사용된다면, 기다리다가 운영체제에 의해 할당되는 것보다 반복 요청이 더 좋을 정도로 스핀 잠금 시간이 충분히 짧다. 스핀 잠금은 리소스 대기 시간이 짧다고 예상되는 경우에 사용된다.

*********************************************************************************************************************************************************

 

AND

MS SQL 일반적인 TIP

SQL Server 2007. 2. 27. 16:35

트랜잭션
트랜잭션은 SQL Server가 변경 사항을 COMMIT하기 전에 얼마나 많은 작업을 수행할 것인지를 다룬다. COMMIT TRANSACTION 문이 실행되기 전에는 다중 문 트랜잭션이 데이터베이스의 내용을 영구적으로 변경하지 않는다. 또한, 다중 문 트랜잭션은 ROLLBACK TRANSACTION문이 실행되었을 경우에 변경 사항을 원래 상태로 되돌릴 수 있다.

 네개의 일괄처리를 포함하는 한 개의 트랜잭션
BEGIN TRAN
 INSERT authors VALUES(etc.)
 Go
 SELECT * FROM authors
 Go
 UPDATE publishers SET pub_id = (etc.)
 GO
COMMIT TRAN
GO

 두개의 트랜잭션을 포함하는 한 개의 일괄처리
BEGIN TRAN
 INSERT authors VALUES(etc.)
 SELECT * FROM authors
COMMIT TRAN
BEGIN TRAN
 UPDATE publishers SET pub_id = (etc.)
 INSERT publishers VALUES(etc.)
COMMIT TRAN
Go

BEGIN TRAN과 COMMIT TRAN문 사이에 있는 명령어들은 하나의 단위로 수행된다.
BEGIN TRAN
 INSERT authors VALUES(etc.)
 SELECT * FROM authors
 UPDATE publishers SET pub_id = (etc.)
COMMIT TRAN
Go
SQL Server에서 트랜잭션을 사용하면 트랜잭션 내에 있는 모든 명령어들은 항상 한 개의 작업 단위로 수행된다. 이런한 트랜잭션은 ACID(원자성, 일관성, 격리성, 영속성) 속성을 갖는다.
 명시적 트랜잭션과 암시적 트랜잭션
기본적으로 SQL Server는 각 트랜잭션들을 독립적으로 취급하고 즉시 커밋한다.(트랜잭션이 개별적으로 전달되었든지 일괄 처리의 일부로 전달되었든지 간에 상관없다.) 논리적으로 커밋될 수 있는 문은 데이터베이스에서 무언가를 변경하는문뿐이다. 따라서, SELECT문을 커밋한다고 말하는 것은 실제로 아무런 의미도 없다. 우리가 트랜잭션에 대해 말할 때는 일반적으로 데이터 변경문(INSERT, UPDATE, DELETE)에 대해 말하는 것이다. 따라서 개별적으로 수행되는 데이터 변경문들은 그 자체로서 “암시적 트랜잭션”이다. 아무리 많은 행들이 변경되더라도, 이 행들은 모두가 변경되거나 아무 것도 변경되지 않을 것이다. 백만 행으로 된 테이블이 업데이트 되고 있는 동안 시스템 고장이 일어난다면, SQL Server가 복구할 때 이미 디스크에 기록된 업데이트 부분을 롤백할 것이고, 데이터베이스의 상태는 업데이트가 일어나지 않았을 때와 동일하게 유지될 것이다.
만일 트랜잭션에 여러 문들을 포함시키고 싶다면, BEGIN TRANSACTION과 COMMIT TRANSACTION 또는 ROLLBACK TRANSACTION 문 사이에 문들을 넣어야 한다. 이를 명시적 트랜잭션이라 한다.
SET IMPLICIT_TRANSACTION ON을 사용함으로써 또는 sp_configure ‘user options’,2를 사용하여 옵션을 전역을 설정함으로써 SQL Server가 암시적으로 트랜잭션을 시작하도록 설정할 수도 있다. 암시적 트랜잭션이 활성화되어 있다면 모든 문들은 트랜잭션의 일부로 간주되고 명시적 COMMIT TRAN이 실행될 때까지 어떤 작업도 커밋되지 않는다. 이것은 일괄처리에 있는 모든 문들이 실행되었을 때에도 마찬가지이다. 다음 일괄 처리에서 COMMIT TRANSACTION을 실행시켜야 한다.

트랜잭션에서 에러 검사하기
개발자들이 SQL Sever에서 저지를 수 있는 가장 일반적인 실수들 중의 하나는 트랜잭션 내에서 일어나는 에러로 인해 트랜잭션이 자동으로 롤백될 것이라고 생각하는 것이다. 우리가 항상 트랜잭션이 원자성을 갖는다고 들어왔기 때문에 이런 오해가 있을 수 있다. 그러나 우리가 트랜잭션 롤백을 일으키는 실패에 대해 말할 때 일반적으로 시스템 고장을 말하고 있는 것이다. 시스템 전체의 기능이 멈추면 SQL Server가 재시작되어야 하고, 원자성을 갖는 복구 과정이 완료되지 않은 트랜잭션들을 롤백시킬 것이다. 그러나 트랜잭션에서 발생한 많은 에러들에 있어서 현재 문만이 중지될 것이고, 트랜잭션에 있는 나머지 문들은 계속해서 처리되고 커밋될 수 있다.

USE Pubs
BEGIN TRAN
 UPDATE authors
 SET state = ‘FL’
 WHERE state = ‘KS’

 UPDATE jobs
 SET min_lvl = min_lvl -10
COMMIT TRAN
SELECT * FROM authors
WHERE state = ‘FL’
트랜잭션은 두개의 UPDATE문을 포함하고 있다. 첫번째 UPDATE문은 한 행에 있는 state값을 ‘FL’로 바꾼다. 두번째 UPDATE문은 jobs테이블에 있는 모든 min_lvl 값들에서 10을 빼려고 시도하지만, min_lvl이 10보다 크거나 같아야 한다는 check 조건이 min_lvl칼럼에 있다. Jobs 테이블에서 min_lvl에서 10을 빼면 제약 조건이 위배되는 컬럼이 있다. 이 한 행 때문에 문 전체가 중지되고 jobs에 있는 어떤 행도 업데이트되지 않지만, 트랜잭션은 롤백되지 않는다. COMMIT이후에 SELECT 문은 state값이 ‘FL’인 저자가 있는 것을 보여준다.

다중문 트랜잭션은 각 문들 이후에 @@ERROR 값을 선택함으로써 에러 발생 여부를 검사해야 한다. 치명적이지 않은 에러가 발생했고 여러분이 이것에 대해 아무런 조치도 하지 않는다면 계속해서 다음 문이 처리된다. 치명적인 에러만이 일괄처리를 자동으로 중지하게 하고 트랜잭션이 롤백되게 만든다.
USE Pubs
BEGIN TRAN
 UPDATE authors
 SET state = ‘FL’
 WHERE state = ‘KS’
 
 IF @@ERROR <> 0 BEGIN
  ROLLBACK TRAN
  GOTO ON_ERROR
 END
 UPDATE jobs
 SET min_lvl = min_lvl - 10
 IF @@ERROR <> 0 BEGIN
  ROLLBACK TRAN
  GOTO ON_ERROR
 END
COMMIT TRAN

ON_ERROR:

SELECT * FROM authors WHERE state = ‘FL’

이 일괄 처리에서 우리는 각 데이터 변경 문들 이후에 에러의 발생 여부를 검사했다. 그러나 에러가 있을 때 단순히 트랜잭션을 롤백시키는 것은 효과적이지 못하다. 왜냐하면, 대부분의 경우에 ROLLBACK TRAN은 문 실행에서 제어 흐름을 바꾸자 않기 때문이다. ROLLBACK TRAN은 현재 트랜잭션에서 완료된 변경 사항들을 되돌리고 트랜잭션 로그에 적절한 항목을 만들 것이다. 그러나 프로그램 흐름은 계속해서 다음 라인으로 이동할 것이다. 이 경우에 GOTO 키워드가 사용되지 않는다면 첫번째 UPDATE에서 에러가 발생했을 때 여전히 계속해서 두번째 UPDATE로 이동할 것이다. 두번째 UPDATE에서 에러가 발생해도 여전히 계속해서 COMMIT TRAN을 실행시키려고 할 것이다. 이 시점에 이미 ROLLBACK TRAN을 실행시켰을 것이기 때문에 더 이상 열려진 트랜잭션이 없을 것이고 COMMIT TRAN 문에서 에러가 발생할 것이다.
구문 에러가 있으면 실행이 시작하기도 전에 항상 일괄 처리 전체가 중지되고, 존재하지 않는 개체에 대한 참조가 있으면 일괄 처리가 해당 시점에서 실행을 멈춘다. 그러나 일괄 처리가 중지되었다고 해서 트랜잭션이 롤백된 것은 아니다. 트랜잭션에 여러 일괄 처리들이 들어 있을 수 있다는 것을 보았다. 에러가 발생했는데 트랜잭션이 롤백되었는지의 여부를 정확히 알수 없다면 시스템 함수 @@TRANCOUNT를 사용하여 트랜잭션의 상태를 검사할 수 있다. @@TRANCOUNT 값이 0보다 크면 활성 트랜잭션이 있는 것이고 여러분이 ROLLBACK TRAN을 실행시킬 수 있다.
동적으로 생성되고 EXECUTE(‘string’)을 사용하여 실행된 일괄 처리의 구문 에러는 실행 시까지 감지되지 않을 것이다. 이 EXECUTE(‘string’)를 일괄 처리들을 중첩시키는 방법으로 생각할 수 있다. 문자열이 여러 문들로 구성되어 있다면 구문이나 개체 참조 에러가 내부 일괄 처리만을 중지시킬 것이다. 외부 일괄 처리는 계속해서 다음 문으로 이동한다.

DECLARE @tablename sysname
SET @tablename = ‘authors’
EXECUTE (‘USE pbus SELECT * FROM ‘ + @tablename + ‘ PRINT “Innser Batch Done”  ‘)
PRINT ‘Outer Batch Done’

이 일괄처리의 분석 시에 에러가 감지되지 않을 것이다. 따라서 SQL Server는 이 일괄처리를 실행시키려고 시도할 것이다. DECLARE와 SET은 성공적으로 실행되겠지만, EXECUTE 내에 중첩된 일괄 처리는 문제를 일으킬 것이다. 테이블 이름 authors가 pubs 데이터베이스에 존재하지 않기 때문에 SELECT문에 에러가 있다. 안쪽 일괄 처리에 있는 세번째 문(Print)이 실행되지 않을 것이다. 그러나 바깥쪽 일괄 처리는 계속해서 진행될 것이다.
제품 환경에서 발생한 에러는 일반적으로 리소스 에러이다. 아마도 이러한 에러를 거의 만나지 못할 것이다. 특히 구성 설정과 환경이 적절한지 확인하기 위해 충분히 테스트했다면 이 에러는 거의 발생하지 않을 것이다. 리소스 부족 에러는 데이터베이스 파일이 꽉 찼을 때나, 프로시저를 충분한 메모리가 없을 때나, 데이터베이스에 연결할 수 없을 때 발생한다. 이런 종류의 치명적 에러가 발생하면 일괄처리가 자동으로 중지된다.

 다음은 일반적으로 관심을 가질만한 에러이다.
개체에 대한 권한 부족
제약 조건위배
행을 업데이트 하거나 삽입하려고 하는 동인 일어난 복제
다른 사용자와 상호 교착 상태
NOT NULL위배
현재 데이터 형식에 적합하지 않은 값

 다음은 세개의 테이블을 생성하는 예제이다.
CREATE TABLE a(a char(1) primary key)
CREATE TABLE b(b char(1) references a)
CREATE TABLE c(c char(1)
Go
CREATE PROC test as
BEGIN TRANSACTION
INSERT c VALUES(‘X’)
  IF(@@ERROR <> 0 ) GOTO on_error
 INSERT b VALUES(‘X’) --참조 실패
  IF(@@ERROR <> 0 ) GOTO on_error
COMMIT TRANSACTION
RETURN(0)

On_error:
ROLLBACK TRANSACTION
RETURN(1)

이 간단한 프로시져는 T-SQL의 강력함을 잘 보여준다. 각 문들 이후에 시스템 함수 @@ERROR는 연결에 대한 값을 반환할 수 있다. @@ERROR 값 0은 에러가 없다는 것을 의미한다. 사용자 제공 데이터가 있을 때 프로시져 테스트가 수행되면 테이블 b로 삽입하려는 INSERT동작은 외래키 위배로 인해 실패할 것이다.(에러 547발생)
따라서 INSERT 문 이후에 @@ERROR는 547로 설정될 것이다. IF(@@ERROR<>0)문은 TRUE로 평가되고, 실행 흐름은 on_error: 레이블로 간다. 여기서 트랜잭션이 롤백된다. RETURN(1)문이 사용되었기 때문에 반환 코드 값이 1이 되면서 프로시져가 종료된다.
대부분의 다른 프로그래밍 언어에서와 마찬가지로 T-SQL에서도 성공이나 실패 여부를 나타내기 위해 프로시져에서 상태 코드를 반환할 수 있다. 반환된 상태 코드는 호출한 루틴에서 검사된다. 그러나 프로시져를 실행시킨 후 상태를 검사하는 것을 잊지 않아야 한다. 단순히 EXEC test를 사용하는 것은 프로시져가 예상대로 수행되었는지에 관한 정보를 직접적으로 제공하지 않을 것이다. 더 좋은 방법은 이 프로시져의 반환 코드를 조사하기 위해 지역 변수를 사용하는 것이다.
DECLARE @retcode int
EXEC @retcode = test
에러가 발생했을 때 강제적으로 일괄 처리를 종료시키기 위해 SQL Server에 SET XACT_ABORT 옵션이 추가되었다. 다음은 에러 발생시 아무것도 커밋되지 않게한다.
CREATE PROC test AS
SET XACT_ABORT ON
BEGIN TRANSACTION
 INSERT c VALUES(‘X’)
 INSERT b VALUES(‘X’) --참조실패
COMMIT TRANSACTION
GO
EXEC test
GO
SELECT * FROM c
--아무 행도 없음..
XACT_ABORT 옵션의 이름은 약간 잘못 지어진 것 같다. 에러가 발생시에 트랜잭션이 아니라 현재 일괄처리가 즉시 중지되기 때문이다. 이것은 치명적인 리소스 에러 발생시와 비슷하다. 한 일괄 처리 내에서 두개의 트랜잭션을 실행시켰다면 두번째 트랜잭션이 실행될 기회를 얻기 전에 일괄 처리가 중지될 것이기 때문에 두번째 트랜잭션은 결코 실행되지 않을 것이다. 우리가 좋은 프로그래밍 습관을 사용하고 앞 프로시져의 반환 상태를 검사하고 싶다고 가정하자.(프로시져는 명시적으로 RETURN문을 사용하지 않더라도 모든 프로시져들은 기본적으로 반환상태 값을 갖는다. 반환 상태 값 0은 SUCCESS를 나타낸다.)
우리는 다음과 같은 일괄 처리를 작성할 수 있다.
DECLARE @retcode int
EXEC @retcode = test
SELECT @retcode
여기서 프로시저에서 에러가 발생하면 SELECT @RETCODE문이 결코 실행되지 않을 것이다.(SET XACT_ABORT 문 때문에 일괄 처리 전체가 중지된다.) 이런 이유 때문에 SET XACT_ABORT를 사용하는 대신 @@ERROR를 검사할 것을 권한다. 각 문들 이후에 @@ERROR를 검사하는 것은 지루한 일이 되지만, 프로시저에서 실행을 더 잘 제어할 수 있게 해준다. 불행히도, SQL Server 2000의 에러 핸들링은 일관되지 못하고 산만해질 수 있다. 예를 들면, “에러가 발생했을 경우에 이것을 수행하라” 의미를 지닌 루틴을 설치할 방법이 없다. 대신 @@ERROR를 검사한 후 GOTO를 사용하는 앞 예제 방식을 사용해야 한다.
또한, 현재 일괄 처리가 중지될 수 있도록 어떤 에러들이 치명적일 것인지를 미리 알아내거나, 다음 문이 실행될 수 있도록 어떤 에러들이 치명적이지 않은지를 미리 알아내는 쉬운 방법이 없다. 대부분의 경우에 severity 수준 값이 16이나 이 이상의 값을 갖는 에러는 치명적이고, 이 에러가 발생했을 경우에 일괄 처리는 중지될 것이다. 존재하지 않는 함수를 참조하는 구문은 severity 수준 값이 15인 에러이지만, 일괄 처리는 여전히 중지된다. @@SERVERITY 같은 함수는 없다. 대신, 마지막 에러의 serverity 수준을 보기 위해 sysmessage 테이블에서 정보를 얻어야 한다. Serverity 수준 값이 16인 일부 에러들(예:첫번째 에러 검사 예제에서 발생한 제약 조건 위배 에러)은 치명적이지 않다.

트랜잭션 격리 수준
트랜잭션이 실행되는 격리 수준은 다른 사용자의 변경에 대한 여러분 애플리케이션의 민감도를 결정한다. 따라서, 격리 수준은 다른 사용자에 의한 변경으로부터 보호하기 위해 트랜잭션이 얼마나 오랫동안 잠금을 유지할 필요가 있는지 결정한다. SQL Server는 네가지 격리 수준을 제공한다.
READ UNCOMMITTED(dirty read)
READ COMMITTED(디폴트)
REPEATABLE READ
SERIALIZABLE
어떤 격리 수준이 설정되어 있느냐에 따라 트랜잭션은 다르게 동작한다.

 READ UNCOMMITTED
READ UNCOMMITTED를 지정하는 것은 트랜잭션 안에서 참조되는 모든 테이블에 대해 NOLOCK 힌트를 사용하는 것과 같다. 이것은 SQL Server의 네가지 트랜잭션 고립화 수준 중에서 가장 엄격하지 못한 수준이다. 이 고립화 수준에서는 Dirty Read(다른 트랜잭션에 의해 수정된, 아직 커밋되지 않은 데이터를 읽는 것)가 허용되고, 반복적이지 않은 읽기(트랜잭션 안에서 읽어 들일 때마다 달라지는 데이터)도 허용된다. 다음 두개의 쿼리를 동시에 실행시켜보자.
Query1
SELECT TOP 5 title_id, qty FROM sales ORDER BY title_id, stor_id
BEGIN TRAN
UPDATE sales SET qty = 0
SELECT TOP 5 title_id, qty FROM sales ORDER BY title_id, stor_id
WAITFOR DELAY ’00:00:05’
ROLLBACK TRAN
SELECT TOP 5 title_id, qty FROM sales ORDER BY title_id, stor_id

Query2
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
PRINT ‘Now you see it..’
SELECT TOP 5 title_id, qty FROM Sales
WHERE qty = 0 ORDER BY title_id, stor_id

IF @@ROWCOUNT > 0 BEGIN
 WAITFOR DELAY ’00:00:05’
PRINT ‘…not you don’t’
SELECT TOP 5 title_id, qty FROM sales WHERE qty = 0 ORDER BY title_id, stor_id
END
첫 번째 쿼리가 실행 중인 동안 두 번째 쿼리를 시작해보면, 첫 번째 쿼리에 의해 수정된 아직 커밋되지 않은 데이터들도 액세스할 수 있음을 알 수 있다. 그 후에, 두 번째 쿼리는 첫 번째 트랜잭션이 종료할 때가지 기다렸다가 같은 데이터들을 다시 읽어들이려 하는데, 첫 번째 트랜잭션에 의한 수정 작업이 이미 롤백 되었기 때문에, 읽어들이려는 데이터는 이미 존재하지 않으며, 따라서 두 번째 쿼리는 비반복적인 읽기를 하게 된다.
 READ COMMITTED
READ COMMITTED는 SQL Server의 디폴트 트랜잭션 고립화 수준으로서, 개발자가 다른 것을 지정하지 않으면 READ COMMITTED 방식으로 동작한다. READ COMMITTED는 액세스하는 데이터들에 공유 락을 설정하여 Dirty Read를 금지하지만, 트랜잭션 안에서 원본 데이터에 대한 수정은 허용한다. 따라서 반복적인지 않은 읽기 혹은 팬텀 읽기(내가 읽어온 데이터를 보고 있을 때 다른 사용자가 로우를 입력하거나 삭제하여, 다음에 같은 데이터를 읽어보면 보지 못했던 로우가 들어있거나 있던 로우가 없어지는 것)가 허용된다.
Query1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
PRINT ‘Now you see it…’
SELECT TOP 5 title_id, qty FROM sales ORDER BY title_id, stor_id
WAITFOR DELAY ’00:00:05’
PRINT ‘…now you don’t’
SELECT TOP 5 title_id, qty FROM sales ORDER BY title_id, stor_id
GO
ROLLBACK TRAN

Query2
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
UPDATE sales SET Qty = 6 where Qty = 5
이 예제에서 sales테이블의 첫 번째 로우에서 qty컬럼값이 첫 번째 쿼리의 읽기 작업마다 달라지는데 이것이 고전적인 비반복적인 읽기다.
 REPEATABLE READ
REPEATABLE READ는 한 트랜잭션이 액세스하는 데이터를 다른 사용자가 수정하지 못하게 락을 설정하지만, 새로운 로우들의 입력은 허용한다. 따라서 트랜잭션 안에서 읽기 작업을 다시 수행하면 팬텀 데이터가 나타날 수 있다.
Query1
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
PRINT ‘Nothing up my sleeve…’
SELECT TOP 5 title_id, qty FROM sales ORDER BY qty
WAITFOR DELAY ’00:00:05’
PRINT ‘…except this rabbit’
SELECT TOP 5 title_id, qty FROM sales ORDER BY qty
GO
ROLLBACK TRAN

Query2
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
INSERT sales VALUES(6380,9999999,GETDATE(),2,’USG-Whenever’,’PS2091’)
실행 결과를 보게 되면, sales테이블은 첫번째 읽을 때 없었던 데이터가 두 번째 읽을 때에는 나타난다. REPETABLE READ는 트랜잭션이 이미 액세스하고 있는 데이터에 대한 수정은 금지하지만, 새로운 로우의 추가는 허용하므로 팬텀 데이터가 발생할 수 있다.
 SERIALIZABLE
SERIALIZABLE은 트랜잭션이 액세스하는 데이터들의 일정 범위에 대해 락을 설정하여 Dirty Read와 팬텀 데이터를 금지시킨다. 이 모드는 SQL Server의 네 가지 트랜잭션 고립화 수준 중에서 가장 엄격한 모드이며, 트랜잭션이 참조하는 모든 테이블에 대해 HOLDLOCK힌트를 사용하는 것과 같다.
Query1
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
PRINT ‘Nothin up my sleeve…’
SELECT TOP 5 title_id, qty FROM sales ORDER BY qty
WAITFOR DELAY ’00:00:05’
PRINT ‘… or in my hat’
SELECT TOP 5 title_id, qty FROM sales ORDER BY qty
ROLLBACK TRAN

Query2
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
INSERT sales VALUES(6380,9999999,GETDATE(),2,’USG_Whenever’,’PS2091’)
ROLLBACK TRAN
이 예제에서 SERIALIZABLE 고립화 수준에 의해 설정된 락에 의해 두 번째 쿼리는 첫 번째 쿼리가 끝나기 전에는 실행되지 않는다. 이런 방법은 물샐틈없는 데이터 일관성을 보장해주지만, 그만큼 동시 사용성을 떨어뜨린다.
 4가지 고립화 수준의 정리
Ÿ READ UNCOMMITTED : Dirty Read를 허용(커밋되지 않은 데이터를 읽는다.), 반복적이지 않은 읽기 발생(읽을때마다 다른 데이터가 발생)
Ÿ READ COMMITTED : Dirty Read는 발생되지 않지만, 다른 사용자가 읽고 있는 데이터에 수정이 가능하므로 반복적이지 않은 읽기나, 팬텀 데이터가 발생된다.
Ÿ REPEATABLE READ : 다른 트랜잭션에서 수정은 불가능하게 락을 설정하지만, 새로운 데이터의 삽입은 가능하므로 팬텀 데이터가 발생된다.
Ÿ SERIALIZABLE : 다른 트랜잭션에서는 해당 트랜잭션이 끝날때까지 데이터를 건들수 없다.

트랜잭션의 다른 특성들
첫재, 여러 행들에 영향을 주는 한 개의 UPDATE나 DELETE나 INSERT/SELECT문은 항상 원자성을 갖는 동작이다. 즉, 이것은 에러 없이 완료되어야 하고, 그렇지 않으면 자동으로 롤백된다. 예를 들어, 모든 행들을 업데이트 하는 한 개의 UPDATE문을 수행했지만, 한 행이 제약 조건을 위배했다면, 아무 행도 업데이트되지 않고 동작이 종료될 것이다. 이러한 문 내에서 각 행들에 대해 에러를 검사할 방법이 없기 때문에 어떤 에러든지 문을 롤백시킨다. 그러나 일괄 처리는 중지되지 않는다. 일괄처리에서 실행은 다음 문으로 계속 진행한다. 이런 일은 한 SELECT문에 기반을 둔 INSERT 동작들(INSERT/SELECT 또는 SELECT INTO)에서도 일어날 것이다.
또는, SQL Server와 관련되지 않은 시스템 고장 때문에 수백 개의 행들을 업데이트하는 한 개의 UPDATE문이 완료되지 않을 수 있다. SQL Server가 한 개의 UPDATE를 원자성을 갖는 작업 단위라고 생각하기 때문에 SQL Server가 다시 시작하고 데이터베이스에 대해 복구 작업을 수행할 때 UPDATE가 전혀 일어나지 않은 것처럼 만들 것이다. 트랜잭션이 커밋되었다는 것을 SQL Server가 확실하게 알고 있지 않다면 트랜잭션이 롤백될것이다. 고장 전에 백만 개의 행들 중에서 999,999,개의 행이 업데이트 되었을 지라도 트랜잭션이 “모두 처리되지 않는다면, 하나도 처리되지 않는(all-or-nothing)” 동작 방식을 따르기 때문에 복구 후에 아무 것도 업데이트되지 않은 상태로 있을 것이다.
트리거에 의한 변경은 항상 데이터 베이스 변경문과 하나의 단위로 취급된다. 예를 들어, 업데이트 트리거가 데이터를 변경하려고 하지만 실패한다면 데이터베이스 변경 동작이 롤백된다. 이 두 동작이 모두 성공해야 하고 그렇지 않으면 아무 것도 성공하지 않는다.

중첩된 트랜잭션 블록
구문 상으로 볼 때, BEGIN TRANSACTION과 COMMIT TRAN(또는, ROLLBACK TRAN)으로 구성된 블록들이 다른 블록 내에 중첩 될 수 있다. 중첩된 저장 프로시저들을 호출하여 이런 종류의 중첩이 존재하게 할 수도 있다. 그러나 예상과는 다르게 우리가 원하는데로 트랜잭션들은 진정으로 중첩되지 않는다. 그러나 우리가 이 동작을 이해할 수 있고 예측할 수 있다. ROLLBACK TRAN은 내부 블록뿐 아니라 모든 수준의 트랜잭션들을 롤백시킨다. 문이 가장 바깥쪽 블록의 일부가 아니라면 COMMIT TRAN은 아무 일도 하지 않는다. COMMIT TRAN이 가장 바깥쪽 블록의 일부라면 이것이 모든 수준의 트랜잭션들을 커밋한다. 따라서, 트랜잭션 블록들이 중첩될 때 COMMIT이나 ROLLBACK의 동작은 일반적인 중첩 방식을 따르지 않는다. 가장 바깥쪽 COMMIT만이 트랜잭션을 커밋할 수 있지만, ROLLBACK은 수준에 관계없이 트랜잭션 전체를 롤백시킬 것이다. 만일 이런식으로 동작하지 않는다면 데이터가 이미 커밋되었을 것이기 때문에 바깥쪽에 있는 트랜잭션의 ROLLBACK이 자신의 작업을 할 수 없을 것이다. 이 동작 방식은 트랜잭션 상태를 검사하지 않아도 저장 프로시저와 트리거가 예측 가능한 방식으로 자동으로 실행될 수 있게 해준다. ROLLBACK TRAN을 수행하는 중첩된 저장 프로시저는 최상위 프로그래밍에 의해 수행된 작업을 포함하여 트랜잭션 전체를 롤백시킬 것이다.
또한, BEGIN TRAN과 COMMIT TRAN(또는 ROLLBACK TRAN) 블록은 실제로 실행되는 것에 의해서만 결정되고, 일괄 처리에 존재하는 것에 의해 결정되지 않는다. 조건부 분기가 일어나고 문들 중의 하나가 실행되지 않는다면 이 문은 블록의 일부가 아니다.
ROLLBACK TRAN에 대한 일반적인 오해는 이것이 제어 흐름을 바꾸어서 저장 프로시저나 일괄 처리에서 즉시 return하게 만든다고 생각하는 것이다, 그러나 제어 흐름은 계속해서 다음 문으로 이동한다. ROLLBACK은 실제 데이터에만 영향을 준다. 이것은 지역 변수나 SET 문에 영향을 주지 않는다. 트랜잭션 동안에 지역 변수가 바뀐다거나 SET 문이 실행된다면 이 변수와 옵션들은 트랜잭션 시작 전의 값으로 되돌아가지 않는다. 변수(테이블 변수 포함)와 SET 옵션들은 트랜잭션 제어의 일부가 아니다.
시스템 함수 @@TRANCOUNT는 실행된 BEGIN TRAN 블록의 깊이를 반환할 것이다. 이런 식으로 COMMIT TRAN과 ROLLBACK TRAN의 동작을 생각할 수 있다. @@TRANCOUNT가 1이거나 1보다 클 때마다 ROLLBACK TRAN의 동작을 생각할 수 있다. @@TRANCOUNT가 1이거나 1보다 클 때마다 ROLLBACK TRAN은 모든 수준의 트랜잭션 블록에 대해 자신의 일을 수행한다. COMMIT TRAN은 @@TRANCOUNT가 1일 때만 변경사항을 커밋한다.
BEGIN TRAN문을 실행시키면 항상 @@TRANCOUNT가 증가한다. 활성화된 트랜잭션이 없다면, @@TRANCOUNT가 0이다. COMMIT TRAN 문을 실행시키면 @@TRANCOUNT값이 줄어든다. ROLLBACK TRAN문을 실행시키면 모든 트랜잭션들이 롤백되고 @@TRANCOUNT가 0으로 설정된다. 열려 있는 트랜잭션이 없을 때 @@TRANCOUNT가 0일 때 COMMIT TRAN이나 ROLLBACK TRAN을 실행시키면 3902 에러나 3903 에러가 발생한다. 이 에러들은 COMMIT이나 ROLLBACK 요청에 대응하는 BEGIN TRANSACTION이 없다는 것을 나타낸다. 이 경우에 @@TRANCOUNT가 감소되지 않기 때문에 0 값 밑으로 내려 갈 수 없다.
사용자에 의한 에러가 아닌 다른 에러들도 치명적일 수 있다. 예를 들어 메모리가 부족하거나, 트랜잭션 로그가 꽉 찼거나 또는, 교착 상태로 인해 종료 되는 일이 발생할 수 있다. 이러한 에러들은 열려 있는 트랜잭션을 자동으로 롤백시킨다. 만일 이런 일이 발생한다면 @@TRANCOUNT는 0을 반환함으로써 열려 있는 트랜잭션이 존재하지 않는다고 알릴 것이다.
교착상태 조건을 처리하기 위해 클라이언트 애플리케이션에서 재시도 로직을 작성하는 방법이 있을 수 있다. 다른 에러 조건들에 대해서는 여러분이 디폴트 에러 동작을 받아들이기로 결정할지 모른다. 시스템 관리가 올바로 이루어지는 상황에서 애플리케이션이 배포된다면 이러한 에러는 존재하지 않거나 거의 발생하지 않아야 한다. 트랜잭션이 열려 있는지 확인하기 위해 ROLLBACK TRAN이나 COMMIT TRAN을 실행시키기 전에 쉽게 @@TRANCOUNT를 검사할 수도 있다.
ROLLBACK 문에서 트랜잭션의 이름이 지정되지 않는 것은 트랜잭션 블록 관련 문제들 때문이다. 만일 롤백에서 최상위 트랜잭션이 아닌 다른 트래잭션의 이름이 지정되면 6401에러가 발생할 것이다. 그러나 BEGIN TRAN 블록에서 트랜잭션의 이름을 지정하는 것은 상관없다. COMMIT에도 이름이 지정될 수 있고, 이 경우에 이것이 기본적으로 NO-OP이기 때문에 최상위에 있는 것과 짝을 이루지 않아도 에러가 발생하지 않는다.
BEGIN TRAN문에 이름을 지정하는 한가지 이유는 COMMIT와 ROLLBACK 문에 주석을 다는 효과를 주기 위한 것이다. ROLLBACK TRAN은 트랜잭션 이름을 필요로 하지 않지만, 트랜잭션 전체를 롤백하고 있다는 것을 강조하기 위한 방법으로 이름을 포함시킬 수 있다.

저장점(Savepoints)
이따금 사용자들이 트랜잭션 블록들을 중첩시켰을 때 결과 동작이 원하는 것과 다를 수 있다. 사용자가 실제로 원하는 것은 트랜잭션에서 저장점이 일어나는 것이다. 저장점은 작업이 부분적으로 롤백되거나 취소될 수 있는 트랜잭션 내의 지점을 나타낸다. 좀더 정확히 말하면, 이것을 “롤백 지점”이라고 부를 수 있다. 저장점은 변경 사항을 데이터베이스에 저장하거나 커밋하지 않는다. COMMIT문만이 이것을 할 수 있다.
SQL Server에서 SAVE TRAN문을 통해 저장점을 사용할 수 있다. 이 문은 @@TRANCOUNT값에 영향을 주지 않는다. 저장점 위치로 롤백해도(트랜잭션 롤백이 아님) @@TRANCOUNT 값에는 영향이 없다. 그러나, 롤백 시에 저장점 이름을 명시적으로 지정해야 한다. 특정 이름을 지정하지 않고 ROLLBACK TRAN을 사용하면 항상 트랜잭션 전체가 롤백될 것이다.
BEGIN TRAN A
 SELECT @@TRANCOUNT
 SAVE TRAN B
 SELECT @@TRANCOUNT
 ROLLBACK TRAN B
 SELECT @@TRANCOUNT
 ROLLBACK TRAN A
 SELECT @@TRANCOUNT

잠금은 SQL Server를 포함한 다중 사용자 데이터베이스 시스템에서 매우 중요한 기능을 수행한다. SQL Server는 동시에 여러 사용자들을 관리하고, 모든 트랜잭션들이 지정된 격리 수준의 특성들을 지키게 한다. 가장 높은 격리 수준인 Serializable에서 SQL Server는 다중 사용자 시스템이 단일 사용자 시스템과 동일한 결과를 만들어내도록 해야 한다. SQL Server는 동일한 시간에 동일한 데이터베이스에 대해 한 사용자의 변경으로 인해 다른 사용자의 작업이 영향을 받는 일이 없도록 자동으로 데이터를 잠금으로써 이것을 가능하게 한다.

잠금 관리자
SQL Server는 여러 가지 모드를 사용하여 데이터를 잠글 수 있다. 예를 들면, 읽기 동작은 공유 잠금을 얻고, 쓰기 동작은 단독 잠금을 얻는다. 업데이트 잠금은 업데이트 동작의 시작 부분에 데이터가 읽힐 때 얻어진다. SQL Server 잠금 관리자는 이 잠금들을 얻고 해제한다. 또한, SQL Server 잠금 관리자는 잠금 모드들 간의 호환성을 관리하고, 교착 상태를 해결하고, 필요할 경우에 잠금 수준을 올린다. 잠금 관리자는 테이블에 있는 잠금과 테이블의 페이지에 있는 잠금과 인덱스 키에 있는 잠금과 데이터의 개별 행들에 있는 잠금을 제어한다. 잠금은 페이지 헤더나 인덱스와 같은 시스템 데이터에도 유지될 수 있다.
잠금 관리자는 두 가지 잠금 시스템을 제공한다. 첫 번째 시스템은 완전하게 공유된 모든 데이터에 영향을 주고, 테이블과 데이터 페이지와 텍스트 페이지와 잎수준 인덱스 페이지에 행 잠금과 페이지 잠금과 테이블 잠금 기능을 모두 제공한다. 두 번째 시스템은 내부적으로 인덱스 동시성 제어와 내부 데이터 구조에 대한 엑세스 제어와 데이터 페이지에 있는 개별 행들의 추출을 위해 사용된다. 이 두 번째 시스템은 래치를 사용하는데, 이것은 잠금보다 리소스를 적게 사용하고 성능을 최적화한다. 모든 잠금 동작을 위해 완전한 기능의 잠금을 사용할 수 있겠지만, 이 잠금들이 복잡하기 때문에 모든 내부적 동작을 위해 이러한 잠금이 사용된다면 시스템이 느려질 것이다. Sp_lock 시스템 저장 프로시저나 syslockinfo테이블로부터 정보를 얻어내는 메커니즘을 사용하여 잠금들을 조사해보면 래치는 볼 수 없고, 완전하게 공유된 데이터에 대한 잠금들의 정보만을 볼 것이다.
잠금은 데이터의 논리적 일관성을 보장하는 반면, 래치는 물리적 일관성을 보장한다. 물리적으로 페이지에 행을 넣거나 데이터를 이동시킬 때 래치가 사용된다. SQL Server는 이 데이터 이동이 간섭 없이 일어날 수 있도록 보장해야 한다.

잠금 관리자와 격리 수준
SQL Server는 ANSI와 ISO에서 지정된 네 가지 트랜잭션 격리 수준을 모두 지원한다. Serializable 격리 수준을 얻기 위해서는 팬텀을 방지해야 한다. 왜냐하면, 트랜잭션의 동작은 트랜잭션이 단일 사용자 시스템에서 동작할 때와 동일해야 하기 때문이다. Serializable 격리 수준을 지원하기 위해 “키 범위 잠금(key-range lock)”이라고 불리는 특별한 형식의 잠금을 사용하여 인덱스 범위를 잠근다. 이런 잠금은 팬텀을 방지하기 위해 트랜잭션 끝까지 유지된다. 어떤 인덱스도 존재하지 않는다면 잠금 관리자가 테이블 잠금을 사용하여 Serializable을 기능을 보장한다.
잠금 관리자는 2단계 잠금 서비스를 제공한다. 2단계 잠금(2PL)과 2단계 커밋(2PC) 프로토콜은 직접적으로 관련되어 있지 않다. 그러나 2PC가 2PL 서비스를 사용해야 한다는 사실은 확실하다. 2PL에는 트랜잭션이 “성장하는” 단계와 “줄어드는” 단계가 있다. 성장하는 단계 동안에는 트랜잭션이 잠금을 얻고, 줄어드는 단계 동안에는 잠금을 해제한다. Serializable 기능을 얻기 위해, 모든 잠금들은 트랜잭션 끝까지 유지된 후 한 번에 모두 제거된다.
COMMITTED READ와 같이 더 낮은 격리 수준에서는 잠금이 더 빨리 해제될 수 있다. 즉 객체 사용이 완료되었을 때 해제될 수 있다. 예를 들어, 테이블에서 데이터 범위가 쿼리되고 있다면 아마도 공유 잠금이 있을 것이다. COMMITTED READ 격리 수준에서 스캔 동작이 한 데이터 항목에서 다음 데이터 항목으로 이동하자마자 공유 잠금이 해제된다. 반면 단독 잠금은 필요할 때 트랜잭션이 롤백될 수 있도록 트랜잭션 끝까지 항상 유지된다.
SERIALIZABLE이나 REPEATABLE READ 격리 수준에서 읽힌 데이터가 변경되지 않는다는 것을 보장하기 위해 또는 트랜잭션이 진행 중인 동안 쿼리 조건을 만족하는 새 행들이 추가될 수 없다는 것을 보장하기 위해 공유 잠금이 트랜잭션 끝까지 유지되어야 한다. 공유 잠금과 마찬가지로 래치도 트랜잭션의 경계에 묶여 있지 않다. 왜냐하면, 래치는 데이터를 직접 잠그기 위해 사용되지 않고 상호 배타성 기능을 제공하기 위해 사용되기 때문이다. 예를 들면, 클러스터된 인덱스가 있는 테이블에 행을 삽입하는 동안 다른 삽입 동작으로 인해 충돌하는 일이 없도록 가까운 인덱스 페이지가 래치된다. 래치는 긴 시간 동안 상호 배타성 기능을 위해서만 필요하다.

스핀잠금
단기적 잠금이 필요한 경우에 SQL Server는 스핀 잠금(spinlock)으로 구현된 래치를 사용하여 상호 배타적 기능을 얻는다. 스핀 잠금은 순수하게 상호 배타성 기능을 위해서만 사용되고, 결코 사용자 데이터를 잠그지 않는다. 스핀 잠금은 래치보다 훨씬 가볍다.(그리고 래치는 데이터 페이지와 인덱스 잎 페이지에 사용되는 오나전한 잠금보다 더 가볍다.) 스핀 잠금은 SQL Server에만 있는 기능이고, 이 프로세서 형식을 구현하기 위해 프로세서 관련 어셈블리 언어가 사용된다. 스핀 잠금은 각 프로세서 형식과 관련된 몇 라인의 어셈블리 언어로 구현된다. 스핀 잠금을 요청한 개체는 이 잠금이 즉시 사용 가능하게 될 때까지 반복적으로 자신의 요청을 전달한다. 스핀 잠금은 일상적으로 자주 사용되지 않는 리소스에 대해 SQL Server내에서 종종 mutex로 사용된다. 리소스가 자주 사용된다면, 기다리다가 운영체제에 의해 할당되는 것보다 반복 요청이 더 좋을 정도로 스핀 잠금 시간이 충분히 짧다. 스핀 잠금은 리소스 대기 시간이 짧다고 예상되는 경우에 사용된다.

교착상태
두 프로세스가 리소스를 기다리고 있는데 상대 프로세스가 리소스를 얻을 수 없게 하기 때문에 어떤 프로세스도 진행할 수 없을 때, 교착 상태(DeadLock)가 일어난다. 진정한 교착 상태는 외부 간섭이 없어도 어떤 프로세스도 진행할 수 없는 상태이다. 교착 상태가 일어나면 자동으로 SQL Server가 개입하여 문제를 해결한다.
SQL Server에서 크게 두 가지 형식의 교착 상태가 발생할 수 있다. 하나는 순환 교착 상태(cycle deadlock)이고, 다른 하나는 변환 교착 상태(conversion deadlock)이다. A 프로세스가 트랜잭션을 시작하고, authors 테이블에서 단독 테이블 잠금을 얻고, publishers 테이블에 대해 단독 테이블 잠금을 요청한다. 동시에 B 프로세스도 트랜잭션을 시작하고, publishers 테이블에서 단독 잠금을 얻고, authors테이블에 대해 단독 잠금을 요청한다. 두 프로세스는 교착 상태가 된다. 각 프로세스들은 상대 프로세스가 필요로 하는 리소스를 유지하고 있다. 외부 개입이 없다면 어떤 프로세스도 진행할 수 없고 영원히 교착 상태에 빠져 있을 것이다.
SQL Server는 자동으로 교착 상태를 감지하고 잠금관리자를 통해 개입한다. SQL Server 2000에서 교착 상태는 잠금이 아닌 다른 리소스를 포함할 수도 있다. 예를 들어, A프로세스가 Table1에서 잠금을 유지하고 있고 여유 메모리가 생기기를 기다리고 있고, B프로세스가 Table1에서 잠금을 얻을 때가지 해제할 수 없는 메모리를 갖고 있다면 프로세스들이 교착 상태에 빠질 것이다. 스레드와 통신 버퍼도 교착 상태와 관련될 수 있다. 래치는 교착 상태 감지와 관련이 엇다. 왜냐하면, SQL Server는 래치를 얻을 때 교착 상태가 발생하지 않는 알고리즘을 사용하기 때문이다. SQL Server는 교착 상태를 감지할 때 한 프로세스의 일괄 처리를 종료시키고, 활성 트랜잭션을 롤백시키고, 교착 상태르르 해결하기 위해 이 프로세스의 잠금을 해제한다.
SQL Server 2000에서 LOCK_MONITOR라고 불리는 스레드가 매 5초마다 시스템에서 교착 상태 발생 여부를 검사한다. 잠금 모니터는 “교착 상태 감지 카운터” 라고 불리는 내부 카운터를 사용하여 시스템에서 더 자주 교착 상태를 검사할 것인지 결정한다. 교착 상태 감지 카운터는 값 3에서 시작하고, 교착 상태가 발생하면 다시 3으로 설정된다. LOCK_MONITOR 스레드가 5초 간격으로 검사할 때 교착 상태가 없으면 교착 상태 감지 카운터 값을 줄인다. 카운터 값이 0보다 크고 한 프로세스가 잠금 리소스를 요청했는데 블로킹 되었다면, 잠금 관리자가 잠금 모니터에게 모든 잠금들에서 교착 상태가 발생하지 않았는지 검사하라고 요청한다. 20초 동안 교착 상태가 발견되지 않으면, 교착 상태 감지 카운터가 0이 되고, 잠금 관리자가 프로세스가 블로킹될 때마다 있었다 교착 상태 감지 요청을 중지한다. 대부분의 시간 동안에 교착 상태 감지 카운터는 0으로 되어 있고 교착 상태 검사는 잠금 모니터의 5초 간격으로만 수행된다.
LOCK_MONITOR 스레드는 순환 대기 잠금들의 목록을 조사함으로써 교착 상태를 검사한다. 이것은 잠금을 유지하고 있는 프로세스와 잠금을 기다리고 있는 프로세스들간의 순환관계를 나타낸다. SQL Server는 이미 수행된 작업량을 고려하여 가장 롤백하기 쉬운 프로세스를 희생 프로세스로 선택한다. 그러나 어떤 동작들은 교착 상태 희생자로 선택될 수 없다. 예를 들면, 트랜잭션을 롤백시키는 것과 관련된 프로세스는 교착 상태 희생자로 선택될 수 없다. 롤백되고 있는 변경 사항들이 중간 상태로 남아 있어서 데이터를 손상시킬 수 있기 때문이다.
SET DEADLOCK_PRIORTY LOW|NORMAL 문을 사용함으로써 교착 상태가 감지되었을 때 특정 프로세스를 희생 프로세스로 만들 수 있다. 프로세스가 LOW 교착 상태 우선 순위를 갖고 있다면 이 프로세스가 루프를 닫은 프로세스가 아니더라도 교착 상태가 감지되었을 때 이 프로세스가 종료된다. 그러나 교착 상태 우선 순위를 HIGH로 설정하는 SET옵션은 없다. 프로세스가 교착 상태 상황에서 항상 살아남을 수 있게 하고 싶을지 모르지만, 이런 기능은 SQL Server에서 아직 구현되어 있지 않다. 프로세스들이 테이블 액세스 순서를 미리 정해 놓았다면 순환 교착 상태를 피할 수 있었을 것이다. 예를 들어, 항상 authors 테이블을 먼저 액세스하고 publishers 테이블을 두번째로 액세스하기로 결정했다면, 한 프로세스가 먼저 테이블에서 단독 잠금을 기다리고 있는 프로세스는 정상적이고 자연스럽다. 기다리는 것은 교착 상태가 아니라는 것을 기억하기 바란다.

잠금모드
SQL Server는 공유 잠금, 단독 잠금, 업데이트 잠금, 내재된 잠금을 포함한 여러가지 잠금 모드를 사용한다.
 공유 잠금
프로세스는 데이터를 읽을 때 자동으로 공유 잠금을 얻는다. 공유 잠금은 테이블이나 페이지나 인덱스 키나 개별 행들에 유지될 수 있다. 많은 프로세스들이 동일한 데이터에 대해 공유 잠금을 유지할 수 있지만, 어떤 프로세스도 공유 잠금이 있는 데이터에서 단독 잠금을 얻을 수 없다. 일반적으로 데이터를 읽자 마자 공유 잠금이 해제되지만, 쿼리 힌트나 다른 트랜잭션 격리 수준을 사용함으로써 이 방식을 바꿀 수 있다.
 단독 잠금
데이터가 삽입이나 업데이트나 삭제 동작에 의해 변경될 때 SQL Server가 이 데이터에 대해 자동으로 단독 잠금을 얻는다. 특정 데이터 리소스에 대해서 한 번에 한 프로세스만이 단독 잠금을 유지할 수 있다. 실제로, 한 프로세스가 데이터 리소스에 대해 잠금을 요청했을 때 다른 프로세스가 이미 이 데이터에 대해 단독 잠금을 유지하고 있다면 나중에 요청한 프로세스가 어떤 종류의 잠금도 얻을 수 없다. 잠금 호환성에 대해 설명할 때 이것에 대해서도 알아보겠다. 단독 잠금은 트랜잭션 끝까지 유지된다. 따라서, 현재 트랜잭션이 커밋되거나 롤백될 때까지 다른 프로세스가 변경된 데이터를 사용할 수 없다. 힌트를 사용함으로써 다른 프로세스들은 단독 잠금이 유지된 데이터를 읽기로 결정할 수 있다.
 업데이트 잠금
업데이트 잠금은 단지 공유 잠금과 단독 잠금의 하이브리드 형태이다. SQL Server가 데이터 변경 동작을 수행하지만 먼저 변경될 리소스를 찾기 위해 테이블을 검색할 필요가 있을 때 이 잠금이 얻어진다. 쿼리 힌트를 사용함으로써 프로세스는 특별히 업데이트 잠금을 요청할 수 있고, 이 경우에 업데이트 잠금은 변환 교착상태를 방지한다. 업데이트 잠금은 현재 데이터를 읽고 있는 다른 프로세스들과 호환될 수 있게 하고, 데이터를 읽은 이후로 데이터가 변경되지 않았다는 것을 확신하면서 나중에 데이터를 변경할 수 있게 해준다. 데이터를 변경하기에는 업데이트 잠금으로 충분치 않다. 변경 시에는 항상 변경되고 있는 데이터 리소스가 단독 잠금을 갖고 있어야 한다. 업데이트 잠금은 나중에 단독 잠금을 요청할 수 있게 하는 관문 역할을 한다.(많은 프로세스들이 리소스에 대해 공유 잠금을 유지할 수 있지만, 한 프로세스만이 업데이트 잠금을 유지할 수 있다.) 프로세스가 리소스에 대해 업데이트 잠금을 유지하고 있는 한, 다른 어떤 프로세스도 이 리소스에 대해 업데이트 잠금이나 단독 잠금을 얻을 수 없다(대신, 동일한 리소스에 대해 업데이트 잠금이나 단독 잠금을 요청하는 다른 프로세스는 기다려야 한다.) 업데이트 잠금을 유지하고 있는 프로세스는 이 리소스에 대해 단독 잠금을 얻을 수 이있다. 업데이트 잠금을 “업데이트하려는” 잠금으로 생각할 수 있다. 실제로, 이것이 이 잠금이 하는 역할이다. 업데이트 잠금 혼자로는 데이터를 업데이트하기에 충분치 않다. 실제로 데이터를 변경하기 위해서는 단독 잠금이 필요하다. 바로 단독 잠금을 얻을 수 있기 때문에 변환 교착 상태를 피할 수 있다.
“업데이트 잠금”이란 이름 때문에 이 잠금의 정확한 역할을 혼동하는 일이 없기 바란다. 업데이트 잠금은 단지 업데이트 동작만을 위한 것이 아니다. SQL Server는 실제 데이터 변경 전에 데이터를 검색해야 하는 데이터 변경 동작을 위해 업데이트 잠금을 사용한다. 이러한 동작에는 조건에 따른 업데이트 및 삭제와 클러스터된 인덱스가 있는 테이블로 삽입하는 것 등이 포함된다. 후자의 경우에 SQL Server는 새 행이 들어갈 올바른 위치를 찾기 위해 먼저 클러스터된 인덱스를 사용하여 데이터를 검색해야 한다. SQL Server는 검색만을 수행하고 있는 동안 데이터를 보호하기 위해 업데이트 잠금을 사용한다. 올바른 위치를 찾고 삽입 동작을 시작한 이후에야 업데이트 잠금이 단독 잠금으로 된다.
 내재된 잠금
내재된 잠금(Intent Lock)은 실제로 특별한 잠금 모드가 아니다. 이것은 앞에서 설명된 모드에 대한 수식어이다. 즉, 내재된 공유 잠금과 내재된 단독 잠금과 내재된 업데이트 잠금을 가질 수 있다. SQL Server가 서로 다른 수준의 단위에서 잠금을 얻을 수 있기 때문에 리소스의 구성 요소가 이미 잠겨있다는 것을 가리키는 매커니즘이 필요하다. 예를 들어, 한 프로세스가 테이블을 잠그려고 한다면 이 테이블의 행(또는 페이지)이 이미 잠겨 있는지 알아내는 방법이 필요하다. 내재된 잠금은 이 목적에 사용된다.
 특별한 잠금 모드
SQL Server는 세가지 잠금 모드를 추가로 제공한다. 이것들은 스키마 안정성 잠금과 스키마 수정 잠금 그리고 대량 업데이트 잠금이다. 쿼리가 컴파일될 때, 스키마 안정성 잠금은 다른 프로세스들이 스키마 수정 잠금을 얻지 못하게 한다.(스키마 수정 잠금은 테이블의 구조가 변경되고 있을 때 얻어진다.) BULK INSERT명령어가 실행될 때 대량 업데이트 잠금이 얻어진다. 또한 복사 동작은 TABLOCK힌트를 사용함으로써 특별한 이 잠금을 요청해야 한다. 또는 테이블이 table lock on bulk load이라고 불리는 테이블 옵션을 TRUE로 설정할 수 있고, 이렇게 한 후 대량 복사 IN이나 BULK INSERT 동작이 자동으로 대량 업데이트 잠금을 요청할 것이다. 여러 연결들이 대량 업데이트 잠금을 요청했고 얻었다면 이 연결들이 병렬로 동일한 테이블에 로드할 수 있다.
SIX 잠금이란 것도 있다. 이 모드는 결코 잠금 관리자에 의해 직접 요청되지 않고, 단지 변환의 결과이다. 트랜잭션이 리소스에 대해 공유(s) 잠금을 유지하고 있고 나중에 내재된 단독(IX)잠금이 필요하다면 잠금모드가 SIX로 표현 될 것이다.
SET TRANSACTION ISOLATION LEVEL REPETABLE READ
BEGIN TRAN
SELECT * FROM bigtable
UPDATE bigtable
SET col = 0
WHERE keycolumn = 100
테이블이 크다고 가정하면 SELECT문이 공유 테이블 잠금을 얻을 것이다.(테이블이 작을 경우는 개별적으로 행 잠금이나 키 잠금을 얻는다.) 이렇게 한 후 UPDATE문은 한 행의 업데이트를 수행하기 위해 한 개의 단독 키 잠금을 얻을 것이고, 키 수준에서 단독(X) 잠금이 있다는 것은 페이지 수준이나 테이블 수준에서 내재된 단독(IX) 잠금이 있다는 것을 의미한다. 테이블은 sp_lock을 통해 SIX를 보여줄 것이다.
 SQL Server의 잠금 모드들
축약어 잠금모드 내부 코드 설명
S 공유잠금 4 다른 프로세스들이 잠겨진 리소스를 읽을 수 있도록 허용하지만, 이 리소스를 변경하는 것을 허용하지 않는다.
X 단독잠금 6 다른 프로세스들이 잠겨진 리소스에 있는 데이터를 읽거나 변경할 수 없게 방지한다.(단, 프로세스가 Read Uncommitted 격리 수준으로 설정되어 있지 않을 경우에.)
U 업데이트잠금 5 다른 프로세스들이 업데이트 잠금이나 단독 잠금을 얻지 못하도록 방지한다.
IS 내재된공유잠금 7 이 리소스의 구성 요소가 공유 잠금으로 잠겨 있다는 것을 나타낸다. 이 잠금은 테이블 수준이나 페이지 수준에서만 얻어질 수 있다.
IU 내재된업데이트잠금 8 이 리소스의 구성 요소가 업데이트 잠금으로 잠겨 있다는 것을 나타낸다. 이 잠금은 테이블 수준이나 페이지 수준에서만 얻어질 수 있다.
IX 내재된단독잠금 9 이 리소스의 구성 요소가 단독 잠금으로 잠겨 있다는 것을 나타낸다. 이 잠금은 테이블 수준이나 페이지 수준에서만 얻어질 수 있다.
SIX 공유및내재된 단독잠금 11 공유 잠금을 유지하고 있는 리소스가 단독 잠금으로 잠겨진 구성 요소도 갖고 있다는 것을 나타낸다.
Sch-S 스키마안정성잠금 2 이 테이블을 사용하고 있는 쿼리가 컴파일되고 있다는 것을 나타낸다.
Sch-M 스키마수정잠금 3 테이블의 구조가 변경되고 있다는 것을 나타낸다.
BU 대량업데이트잠금 13 대량 복사 동작이 데이터를 테이블로 복사하고 있고 TABLOCK힌트가 적용되고 있을 때 사용된다.
   

“키 범위 잠금”이라고 불리는 잠금 모드는 Serializable 격리 수준에서 데이터 범위를 잠그려고 할 때만 얻어진다. 키 범위 잠금에는 9가지 형식이 있고, 각 형식들은 두 부분으로 된 이름을 갖는다. 첫 번째 부분은 연속 인덱스 키들 사이의 데이터 범위에 있는 잠금 형식을 나타내고, 두 번째 부분은 키 자체에 있는 잠금 형식을 나타낸다.
 SQL Server의 키범위 잠금
축약어 잠금모드의 내부코드 설명
RangeS-S 14 키 사이의 범위에 있는 공유 잠금 범위 끝의 키에 있는 공유 잠금
RangeS-U 15 키 사이의 범위에 있는 공유 잠금 범위 끝의 키에 있는 업데이트 잠금
Rangeln-Null 16 키 사이의 범위에서 삽입을 방지하는 단독 잠금 키 자체에는 어떤 잠금도 없다.
RangeX-X 22 키 사이의 범위에 있는 단독 잠금 범위 끝의 키에 있는 단독 잠금
Rangeln-S 17 S잠금과 Rangeln_NULL잠금에 의해 만들어진 변환 잠금
Rangeln-U 18 U잠금과 Rangeln_NULL잠금에 의해 만들어진 변환 잠금
Rangeln-X 19 X잠금과 Rangeln_NULL잠금의 변환
Rangeln-U 20 Rangeln_NULL잠금과 RangeS_S잠금의 변환
RangeX-U 21 Rangeln_NULL잠금과 RangeS_U잠금의 변환

잠금 단위(Granularity)
SQL Server는 테이블 수준이나 페이지 수준이나 행 수준에서 사용자 데이터 리소스를 잠글 수 있다. SQL Server는 인덱스 키와 인덱스 키의 범위를 잠글 수 있다. SQL Server는 인덱스 키와 인덱스 키의 범위를 잠글 수도 있다. 테이블이 클러스터된 인덱스를 갖고 있다면 데이터 행들이 클러스터된 인덱스의 잎 수준에 있고, 이것들이 행 잠금 대신 키 잠금을 통해 잠긴다.
Syslockinfo  테이블은 잠긴 리소스의 형식(행, 키, 페이지 등), 잠금 모드, 특정 리소스의 식별자를 저장함으로써 각 잠금들을 추적한다. 프로세스가 잠금을 요청할 때 SQL Server는 요청된 잠금과 syslockinfo 테이블에 이미 있는 리소스를 비교하는 리소스 형식과 식별자에서 정확히 일치하는 것을 찾는다(정확히 일치하는 것을 찾기 위해 잠금 모드가 같을 필요는 없다.) 그러나 한 프로세스가 authors 테이블에서 단독 잠금 행을 갖고 있을 때 다른 프로세스가 authors 테이블 전체에 대해 잠금을 얻으려고 할지도 모른다. 이것들이 서로 다른 리소스이기 깨문에 syslockinfo에 이미 추가로 정보가 저장되어 있지 않는 한 SQL Server는 정확히 일치하는 것을 찾지 못한다. 이런 상황을 위해 내재된 잠금이 존재한다. Authors테이블의 행에서 단독 잠금을 가진 프로세스는 이 행을 포함하고 있는 “페이지”에 대해서 내재된 단독 잠금을 갖고, 행을 포함하고 있는 “테이블”에 대해서도 내재된 단독 잠금을 갖는다. 두번째 프로세스가 테이블에 대해 단독 잠금을 얻으려고 시도할 때, 동일한 잠금 리소스(authors 테이블)에 대해 syslockinfo 테이블에서 충돌하는 행이 있다는 것을 발견한다. 이미 잠겨 있는 리소스에 대한 모든 잠금 요청이 충돌을 일으키는 것은 아니다. 호환되지 않은 잠금 모드로 한 프로세스에 의해 이미 잠겨 있는 리소스에 대해 다른 프로세스가 잠금을 요청할 때, 충돌이 일어난다. 예를 들면, 공유 잠금은 서로 호환되기 때문에 두 프로세스가 동일한 리소스에 대해 공유 잠금을 얻을 수 있다.
키 잠금 : SQL Server 2000은 두 가지 종류의 키 잠금을 지원하고, 이들의 사용은 현재 트랜잭션의 격리 수준에 따라 달라진다. 격리 수준이 Read Committed나 Repeatable Read이면, SQL Server가 쿼리를 처리하는 동안 액세스된 실제 인덱스 키들을 잠그려고 한다. 클러스터된 인덱스가 있는 테이블에서 데이터 행들은 인덱스의 잎 수준에 있고, 얻어진 키 잠금을 볼 것이다. 테이블이 힙이면 클러스터되지 않은 인덱스에 대해 키 잠금을 보고 실제 데이터에 대해 행 잠금을 볼 것이다.
격리 수준이 Serilizable이면 특별한 상황이 발생한다. 우리는 팬텀을 방지하고 싶다. 즉, 우리가 트랜잭션 내에서 데이터 범위를 스캔했다면, 어느 누구도 스캔된 범위로 값을 삽입할 수 없도록 테이블을 충분히 잠글 필요가 있다.
BEGIN TRAN
SELECT * FROM employees
WHERE salary BETWEEN 30000 AND 50000
명시적 트랜잭션에서 위와 같이 실행하면 트랜잭션이 끝나기 전에 30000-50000사이의 salary값을 가진 새 행이 삽입되지 않도록 잠금을 얻어야 한다. 7.0이전에는 SQL Server가 페이지 전체나 테이블 전체를 잠금으로써 이것을 보장했다. 그러나 많은 겨우에 이것은 너무 지나친 제한이었다. 실제 WHERE절에서 지정된 것보다 더 많은 데이터가 잠겼다. SQL Server 2000은 키 범위 잠금을 사용한다. 이것은 인덱스에 있는 특정 키 값과 연관되어 있고, 이 키와 인덱스의 이전 키 사이에 있는 모든 값들이 잠기다는 것을 나타낸다.
Employee 테이블의 lastname 필드에 인덱스가 있다고 가정하고 프로세스의 고립화 수준이 Seriializable에서 다음을 실행해 보자
SELECT *
FROM Employee WHERE last_name BETWEEN ‘Delaney’ AND ‘Dulaney’
Dalls, Donvam Dulut가 테이블에서 순차적으로 있는 잎 수준 인덱스 키들이라면, Donovan과 Dullas가 키 범위 잠금을 얻는다.(그러나 Donovan에 해당하는 한 행만이 결과 집합에서 반환된다.) 키 범위 잠금은 두 키 범위 잠금으로 끝나는 범위에 어떤 것도 삽입되지 않게 방지한다. Dallas보다 크고 Donovan보다 작거나 같은 어떤 값도 삽입될 수 없고, Donovan보다 크고 Duluth보다 작거나 같은 어떤 값도 삽입될 수 없다 키 범위 잠금에서 간격을 결정할 때 이전 순차적 키에서 시작하는 점은 열린 상태이고 잠금이 있는 키에서 끝나는 점은 닫힌 상태이다. 키 범위 잠금은 완전하지 않지만, 페이지나 테이블 전체를 잠그는 것보다 훨씬 더 큰 동시성을 제공한다.

잠금 리소스들
잠금은 “익스텐트”에서도 수행된다. 테이블이나 인덱스가 성장할 필요가 있고 새 익스텐트가 할당되어야 할 때 이 잠금이 자동으로 일어난다. 익스텐트 잠금을 또 다른 형식의 특수 목적 래치로 생각 할 수 있지만, 이것은 sp_lock 프로시저의 출력 결과에 나타난다. 익스텐트는 공유 익스텐트 잠금과 단독 익스텐트 잠금을 모두 갖는다.
Sp_lock의 출력 결과를 조사할 때 대부분의 프로세스들이 적어도 한 데이터베이스에서 잠금을 유지하고 있다는 것에 주목하기 바란다. 실제로 master나 tempdb가 아닌 다른 데이터베이스에서 대해 DB잠금을 가질 것이다. 이것은 항상 공유 잠금이고, 데이터베이스에 대가 언제 사용중인지 알아내기 위해 SQL Server에 의해 사용된다. SQL Server는 데이터베이스가 제거되거나 복원되거나 닫힐 수 있는지 결정할 때 DB잠금을 검색한다. Master와 tempdb는 제거되거나 닫힐 수 없기 때문에 이 데이터베이스들에서는 DB잠금이 필요하지 않다. 또한, tempdb는 결코 복원되지 않고, master 데이터베이스를 복원하기 위해서는 서버 전체가 단일 사용자 모드로 시작되어야 하기 때문에 DB잠금이 불필요하다. 일반적으로 여러분이 익스텐트 잠금과 데이터베이스 잠금에 대해 관심을 가질 필요가 없지만, sp_lock을 실행시키거나 syslockinfo를 조사하면 이것들을 볼 수도 있을 것이다.
 애플 리케이션 잠금
잠금에 관한 정보를 저장하고 호환되지 않는 잠금들을 검사하기 위해 SQL Server가 사용하는 방법은 매우 간단하고 확장 가능하다. 여러분도 알고 있듯이 SQL Server 잠금 관리자는 잠긴 개체에 대해 아무 것도 모른다. 잠금 관리자는 항목의 실제 구조를 알지 않고 단지 리소스를 나타내는 문자열만을 다룬다. 두 프로세스가 동일한 리소스에 대해 호환되지 않은 잠금을 얻으려고 한다면 블로킹이 일어날 것이다.
SQL Server 개발자들이 행과 페이지와 테이블뿐만 아니라 컬럼들도 개별적으로 잠글 수 있게 했다면 이들은 컬럼 잠금들에 대한 내부 코드 번호를 결정한 후 이것을 리소스 목록에 추가했을 것이다.
SQL Server 2000은 새 잠금 리소스를 추가하는 대신, 잠겨질 수 있는 리소스를 확장하게 한다. “블로킹을 감지하고 교착 상태를 만들기 위해 제공된 메커니즘”을 이용할 수 있고, 원하는 것을 잠글 수 있다. 이 잠금 리소스들은 “애플리케이션 잠금”이라고 불린다. 애플리케이션 잠금을 정의하기 위해 잠그고 있는 리소스의 이름과 모드와 소유자와 만료시간을 지정한다.
두 리소스들이 동일한 데이터베이스에서 동일한 이름과 동일한 소유자를 갖는다면 이들은 동일한 리소스로 간주되고 블로킹되기 쉽다. “잠금 소유자(lock owner)”는 세션이나 트랜잭션이나 커서를 의미한다는 것을 기억하기 바란다. 애플리케이션 잠금에서 가능한 소유자는 트랜잭션과 세션뿐이다. 요청된 잠금들의 모드가 서로 호환된다면 동일한 리소스에 대해 요청된 잠금들이 모두 허용될 수 있다. 예를 들어 한 번에 한 사용자만이 실행시켜야 하는 저장 프로시저가 있다고 가정하자. 특별한 잠금을 얻는 sp_getapplock 프로시저를 사용함으로써 이 프로시저를 잠글 수 있다. 이 잠금은 누군가가 이 프로시저를 사용하고 있다는 것을 의미 한다. 프로시저가 완료되었을 때, sp_releaseapplock을 사용하여 이 잠금을 해제할 수 있다.
EXEC sp_getapplock ‘ProcLock’, ‘Exclusive’, ‘session’
EXEC MySpecialProc <매개변수 목록>
EXEC sp_releaseapplock ‘ProcLock’, ‘session’
모든 세션이 규약을 따르고 sp_getapplock을 실행시키려고 하기 전에 이 프로시저에 대한 권한을 요청하기 위애 sp_getapplock을 사용한다면 sp_releaseapplock을 사용하여 잠금이 해제될 때까지 또는 세션이 종료될 때까지 어떤 세션도 이 프로시저를 실행 시킬 수 없다. SQL Server는 ProcLock 리소스가 무엇을 의미하는지 모른다. SQL Server는 단지 요청된 다른 잠금들과 비교하기 위해 사용될 행을 syslockinfo 테이블에 추가한다. 프로시저 자체는 실제로 잠겨지지 않는다는 것에 주목하기 바란다. 다른 사용자나 애플리케이션이 이것이 특별한 프로시저라는 것을 모르고 애플리케이션 잠금을 얻지 않고서 MySpecialProd을 실행시키려고 한다면, SQL Server는 세션이 이 프로시저를 실행시키지 못하도록 막지 않을 것이다.
이 프로시저들에서 사용된 리소스 이름은 255 문자 길이 한도 내에서 어떤 식별자이든지 될 수 있다. 동일한 리소스에 대해 다른 요청들과의 호환성을 검사하기 위해 사용될 수 있는 잠금 모드들은 Shared, Update, Exclusive, IntentExclusive, IntentShared이다. 디폴트 모드는 없다. 세번째 매개 변수인 잠금 소유자로 사용할 수 있는 값들은 transaction(디폴트)과 session이다. 소유자가 transaction인 잠금은 사용자 정의 트랜잭션에서 얻어져야 하고, 이것은 sp_releaseapplock을 호출할 필요 없이 트랜잭션이 끝날 때 자동으로 해제될 것이다. 소유자가 session인 잠금은 세션이 종료될 때에만 자동으로 해제될 것이다.
 잠금 리소스 식별하기
잠금 관리자가 요청된 잠금이 허용될 수 있는지를 알아내려고 할 때, 상충되는 모드의 잠금이 이미 존재하는지 알아내기 위해 syslockinfo테이블을 검사한다. 잠금 관리자는 데이터베이스 ID(dbid)와 개체 ID(objid)와 잠긴 리소스의 형식과 잠금에 의해 참조되는 특정 리소스에 대한 설명을 봄으로써 잠금을 비교한다. 잠금 관리자는 리소스 설명의 의미에 대해 아무 것도 모른다. 잠금 관리자는 단순히 일치하는 것을 찾기 위해 잠금 리소스에 관한 문자열을 비교한다. 일치하는 것을 찾으면 리소스가 이미 존재한다는 것을 알 수 있다.
SQL Server에서 잠글 수 있는 리소스들
리소스 축약어 리소스의 내부코드 리소스 기술 예 
데이터 베이스 DB 2 없다. 잠긴 모든 리소스에 대해서 데이터베이스는 항상 dbid컬럼에 나타난다.  
테이블 TAB 5 테이블ID 261575970 (sp_lock은 리소스 기술 컬럼에 있는 테이블 ID를 보여주는 것이 아니라, 자신의 컬럼에 있는 테이블 ID를 보여준다.
익스텐트 EXT 8 익스텐트의 첫번째 페이지의  파일번호:페이지번호 1:96
페이지 PAG 6 실제 테이블이나 인덱스 페이지의 파일번호:페이지번호 1:104
인덱스키 KEY 7 모든 키 구성 요소들과 위치 지정자로부터 유도된 해싱 값. C1컬럼과 C2컬럼에 인덱스가 있는 클러스터되지 않은 인덱스에 대해서 해싱 값은 c1,c2,RID로부터 만들어진 값을 포함할것이다. Ac0001a10a00
인덱스의 키범위 KEY 7 인덱스 키와 동일 Ac0001a10a00
행 RID 9 실제 행의 파일번호:페잊번호:슬롯번호 1:161:3
애플리케이션 APP 10 잠금 이름으로부터 유도된 해싱 값 MyPr8adaea5f

잠금 지속 기간
잠금이 유지되는 시간의 길이는 주로 잠금 모드와 사용중인 트랜잭션 격리 수준에 따라 달라진다. SQL Server의 디폴트 격리 수준은 Read Committed이다. 이 격리 수준에서 SQL Server가 공유 잠금으로 잠긴 데이터를 읽고 처리하자마자 이 공유 잠금이 해제된다. 단독 잠금은 트랜잭션 끝까지 유지된다. 업데이트 잠금도 트랜잭션 끝까지 유지된다. 트랜잭션 격리 수준이 Repeatable Read나 Serializable이면 공유 잠금의 지속 기간은 단독 잠금과 동일하다. 즉, 트랜잭션이 끝날 때가지 공유 잠금이 해제되지 않는다. Repeatable Read나 Serializable이면 공유 잠금의 지속 기간은 단독 잠금은 동일하다. 즉, 트랜잭션이 끝날 때까지 공유 잠금이 해제되지 않는다.
트랜잭션 격리 수준을 변경할 뿐 아니라 쿼리 힌트를 사용함으로써 잠금 지속 기간을 제어할 수 있다.

잠금 소유권
잠금 지속 기간은 잠금 소유권에 의해 영향을 받을 수 있다. 세 가지 형식의 잠금 소유자가 있다. 이것들은 트랜잭션, 커서, 세션이다. Syslockinfo 테이블의 req_ownertype컬럼을 통해 이것들을 볼 수 있다.(sp_lock에는 나타나지 않는다) req_ownertype값이 1이면 트랜잭션이 잠금을 소유한 것이고 이 잠금의 지속 기간은 앞 섹션에서 설명된 것과 같다. 실제로, 잠금에 대해 설명한 대부분의 내용은 트랜잭션이 소유한 잠금들에 관한 것이다. Req_ownertype 값이 2이면 커서가 잠금을 소유한 것이다. SCROLL_LOCK잠금 모드를 사용하여 커서를 열었다면, 다음 행을 불러오거나 커서가 닫힐 때까지 모든 행에서 커서 잠금이 유지된다. 다음 FETCH 전에 트랜잭션이 커밋되더라도, 커서 잠금은 해제되지 않는다.
Req_ownertype값이 3이면 세션이 잠금을 소유한 것이다. 세션 잠금은 트랜잭션의 범위 바깥에 있는 프로세스를 위해 얻어지는 것이다.

잠금 보기
현재 시스템에 존재하는 잠금과 대기중인 잠금들을 보려면 syslockinfo 시스템에 테이블을 조사하거나 sp_lock 시스템 저장 프로시저를 실행시킨다. Syslockinfo 테이블은 실제로 시스템 테이블이 아니다. 잠금이 디스크에 유지되지 않기 때문에 syslockinfo 테이블도 디스크에 유지되지 않는다. 대신, syslockinfo가 쿼리될 때마다 이것은 잠금 관리자의 현재 잠금 카운트에 근거한 테이블 형식으로 존재한다. 잠금 활동을 보는 다른 방법은 SQL Server 엔터프라이즈 관리자의 그래픽 환경에서 잠금 상태를 보는 것이다.
어떤 경우에는 sp_lock의 출력 결과량이 매우 많을 수 있다. 한두 프로세스 ID값을 지정함으로써 이 출력 결과를 줄일 수 있다. 이렇게 하면 sp_lock은 이 프로세스들에 의해 유지되는 잠금들만을 보여줄 것이다. @@spid 시스템 함수를 사용함으로써 특정 연결에 대한 프로세스 ID를 알수 있다. Sp_lock을 실행시키고 현재 연결만을 지정할 수 있다.
EXEC sp_lock @@spid
그러나 출력 결과를 현재 연결의 잠금들로 제한하더라도, 이따금 원하는 것 이상의 출력 결과들이 얻어질 수 있다. SQL Server는 이 변환을 수행하기 위해 master 데이터베이스의 spt_values 테이블을 조회 테이블로 사용한다. 여러분이 serializable 격리 수준에 있다면 tempdb에 있는 임시 테이블뿐 아니라 master 데이터베이스에 있는 이 테이블에서도 잠금이 유지될 수 있다. 추가로 있는 이 잠금들로 인해 데이터에 대한 잠금을 이해하는 데 어려움이 있을 수 있다.
 READ COMMITTED SELECT 잠금보기
USE PUBS
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
 SELECT * FROM authors
 WHERE au_lname = 'Ringer'
 EXEC sp_lock
COMMIT TRAN

spid   dbid   ObjId       IndId  Type Resource         Mode     Status
------ ------ ----------- ------ ---- ---------------- -------- ------
55     5      0           0      DB                    S        GRANT
55     1      85575343    0      TAB                   IS       GRANT
dbid가 1인 것은 master DB인데, 앞으로는 생략될 것이다.
여기서 authors의 dbid는 5이다. DB에 대해 공유잠금만 걸려 있는 것을 볼수 있다.
기본적으로 공유 잠금은 데이터를 읽자마자 해제되기 때문에 sp_lock이 실행될 시점에는 이 잠금이 더 이상 유지되지 않는다.
 REPEATABLE READ SELECT 잠금보기

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
 SELECT * FROM authors
 WHERE au_lname = 'Ringer'
 EXEC sp_lock
COMMIT TRAN
spid   dbid   ObjId       IndId  Type Resource         Mode     Status
------ ------ ----------- ------ ---- ---------------- -------- ------
….
55     5      1977058079  2      KEY  (62039d7395e8)   S        GRANT
55     5      1977058079  1      KEY  (04015bb61919)   S        GRANT
55     5      1977058079  1      PAG  1:179            IS       GRANT
55     5      1977058079  2      PAG  1:195            IS       GRANT
55     5      1977058079  1      KEY  (1201b4159b48)   S        GRANT
55     5      1977058079  2      KEY  (6e021955d8e9)   S        GRANT
55     5      1977058079  0      TAB                   IS       GRANT

authors 테이블이 클러스터된 인덱스를 갖고 있기 때문에 데이터 행들이 모두 잎 수준에 있는 인덱스 행들이다. 각 행들에 있는 잠금들은 행 잠금 대신 키 잠금으로 표시된다. 이것들은 클러스터되지 않은 인덱스의 잎 수준에 있는 키 잠금들이기도 하다. Authors 테이블에서 클러스터되지 않은 인덱스는 au_lname 컬럼에 있고, 이것은 지정된 행들을 찾기 위해 탐색되고 있는 인덱스이다. Indid 값이 1인 경우는 데이터 행이고, 클러스터되지 않은 인덱스 행의 Indid 값은 2이다. 트랜잭션 격리 수준이 Repeatable read이기 때문에 트랜잭션이 끝날때까지 공유 잠금이 유지된다. 두 개의 행들과 두 개의 인덱스 행들은 공유(S)잠금을 갖고, 데이터 및 인덱스 페이지와 테이블 자체는 내재된 공유(IS)잠금을 갖는다.
 Serializable SELECT 잠금보기
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
 SELECT * FROM authors
 WHERE au_lname = 'Ringer'
 EXEC sp_lock
COMMIT TRAN

spid   dbid   ObjId       IndId  Type Resource         Mode     Status
------ ------ ----------- ------ ---- ---------------- -------- ------
55     5      1977058079  2      KEY  (62039d7395e8)   RangeS-S GRANT
55     5      1977058079  1      KEY  (04015bb61919)   S        GRANT
55     5      1977058079  1      PAG  1:179            IS       GRANT
55     5      1977058079  2      PAG  1:195            IS       GRANT
55     5      1977058079  1      KEY  (1201b4159b48)   S        GRANT
55     5      1977058079  2      KEY  (6e021955d8e9)   RangeS-S GRANT
55     5      1977058079  0      TAB                   IS       GRANT
55     5      1977058079  2      KEY  (4903312f82bf)   RangeS-S GRANT
Serilizable 격리 수준에서 유지되는 잠금은 Repetable Read 격리 수준에서 유지되는 잠금과 거의 동일하다. 주된 차이점은 잠금의 모드이다. RangeS-S는 키 자체에 있는 잠금뿐 아니라 키 범위 잠금을 나타낸다. 앞 부분 RangeS는 잠금을 유지하고 있는 키와 이전 키 사이의 키 범위들에 있는 잠금이다. 키 범위 잠금은 다른 트랜잭션들이 이 쿼리 조건을 만족하는 새 행들을 테이블에 삽입할 수 없게 방지한다. 즉, last name이 Ringger인 어떤 새 행도 삽입될 수 없다. 키 범위 잠금은 au_lname, au_fname(Indid=2)에 있는 클러스터 되지 않은 인덱스의 범위에서 유지된다. 왜냐하면, 이것이 조건에 맞는 행들을 찾기 위해 사용된 인덱스이기 때문이다. 클러스터되지 않은 인덱스에서 서로 다른 세 가지 범위가 잠길 필요가 있기 때문에 이 인덱스에서 세 가지 키 잠금들이 있다. SQL Server는 인덱스의 첫번째 Ringer 이전의 키에서부터 첫번째 Ringer 이전의 키에서부터 첫번째 Ringer까지의 범위를 잠가야 하고, Ringer의 두 인스턴스 사이에 있는 범위를 잠가야 하고, 두번째 Ringer와 다음 키 사이의 범위를 잠가야 한다.(실제로, Ringer와 이전 키 Panteley 사이 그리고 Ringer 다음 키 Smith 사이에서 어떤 것도 삽입될 수 없다. 예를 들면 name이 Pike나 Singh인 저자를 삽입할 수 없다.)
 READ COMMITTED UPDATE 잠금보기
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
BEGIN TRAN
 UPDATE authors
 SET contract = 0
 WHERE au_lname = 'Ringer'
 EXEC sp_lock
ROLLBACK TRAN
spid   dbid   ObjId       IndId  Type Resource         Mode     Status
------ ------ ----------- ------ ---- ---------------- -------- ------
55     5      1977058079  1      KEY  (04015bb61919)   X        GRANT
55     5      1977058079  1      PAG  1:179            IX       GRANT
55     5      0           0      DB                    S        GRANT
55     5      1977058079  1      KEY  (1201b4159b48)   X        GRANT
55     5      1977058079  0      TAB                   IX       GRANT
클러스터된 인덱스의 잎 수준에 있는 두 행들은 X잠금(단독잠금)으로 잠기고, 페이지와 테이블은 IX잠금으로 잠긴다. SQL Server는 업데이트될 행들을 찾는동안 실제로 업데이트 잠금을 얻는다. 그러나 실제 업데이이트가 수행될 때 이 잠금들이 X잠금으로 변환되고, sp_lock이 실행되는 시점에는 업데이트 잠금이 사라진다. 실제로 쿼리 힌트를 사용하여 업데이트 잠금을 강제로 유지시키지 않는 한 sp_lock의 출력 결과에서 이것들을 볼 수는 없다.
 Serilizable 수준에서 인덱스를 사용한 UPDATE 잠금보기
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
 UPDATE authors
 SET contract = 0
 WHERE au_lname = 'Ringer'
 EXEC sp_lock
ROLLBACK TRAN
spid   dbid   ObjId       IndId  Type Resource         Mode     Status
------ ------ ----------- ------ ---- ---------------- -------- ------
55     5      1977058079  2      KEY  (62039d7395e8)   RangeS-U GRANT
55     5      1977058079  1      KEY  (04015bb61919)   X        GRANT
55     5      1977058079  1      PAG  1:179            IX       GRANT
55     5      0           0      DB                    S        GRANT
55     5      1977058079  2      PAG  1:195            IU       GRANT
55     5      1977058079  1      KEY  (1201b4159b48)   X        GRANT
55     5      1977058079  2      KEY  (6e021955d8e9)   RangeS-U GRANT
55     5      1977058079  0      TAB                   IX       GRANT
55     5      1977058079  2      KEY  (4903312f82bf)   RangeS-U GRANT
여기서도 키 범위 잠금이 “관련 행들을 찾기 위해 사용된 클러스터되지 않은 인덱스”에 있다는 것에 주목해야 한다. 범위 간격 자체는 삽입을 방지하기 위해 공유잠금만을 필요로 하지만, 다른 프로세스들이 검색된 키들을 업데이트할 수 없도록 검색된 키들은 U 잠금을 갖는다. 테이블 자체(Indid = 1 클러스터인덱스)에 있는 키들은 실제 변경 작업이 수행될 때 단독 잠금을 얻는다.
 Serilizable 수준에서 인덱스를 사용하지 않고 UPDATE 잠금보기
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
 UPDATE authors
 SET contract = 0
 WHERE state = 'UT'
 EXEC sp_lock
ROLLBACK TRAN
spid   dbid   ObjId       IndId  Type Resource         Mode     Status
------ ------ ----------- ------ ---- ---------------- -------- ------
55     5      1977058079  1      KEY  (04015bb61919)   RangeX-X GRANT
55     5      1977058079  1      KEY  (02010b688383)   RangeS-U GRANT
55     5      0           0      DB                    S        GRANT
55     5      1977058079  1      KEY  (1001898f02b5)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0801c4f7a625)   RangeS-U GRANT
55     5      1977058079  1      PAG  1:179            IX       GRANT
55     5      1977058079  1      KEY  (0e01733a43ca)   RangeS-U GRANT
55     5      1977058079  1      KEY  (02014f0bec4e)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0501dc9d152f)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0b018636f9dc)   RangeS-U GRANT
55     5      1977058079  1      KEY  (1201b4159b48)   RangeX-X GRANT
55     5      1977058079  1      KEY  (0401c963b692)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0c01f170d106)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0601d808a263)   RangeS-U GRANT
55     5      1977058079  1      KEY  (070199a50c2d)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0701a9d28094)   RangeS-U GRANT
55     5      1977058079  1      KEY  (10018e6baefb)   RangeS-U GRANT
55     5      1977058079  1      KEY  (100198c3f985)   RangeS-U GRANT
55     5      1977058079  1      KEY  (ffffffffffff)   RangeS-U GRANT
55     5      1977058079  0      TAB                   IX       GRANT
55     5      1977058079  1      KEY  (0c019fc272ba)   RangeS-U GRANT
55     5      1977058079  1      KEY  (100125852812)   RangeS-U GRANT
55     5      1977058079  1      KEY  (06013d38d450)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0d01ac7ad21d)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0101aedb232b)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0601989a35e6)   RangeS-U GRANT
55     5      1977058079  1      KEY  (0901ce1ba5f0)   RangeS-U GRANT
여기에 있는 잠금은 앞 예제에 있는 것과 비슷한다. 단, 모든 잠금들이 테이블 자체(Indid = 1)에 있다는 것이 다르다. 테이블 전체에 있는 클러스터된 인덱스를 스캔해야 하기 때문에 모든 키들이 초기에 RangeS-U잠금을 얻고, 두 행이 변경 되었을 때 이 키들에 있는 잠금들이 RangeX-X잠금으로 변환되었다.
 테이블 생성시 잠금
테이블이 생성될 때, SQL Server는 이 테이블에 관한 정보를 기록하기 위해 sysobjects와 syscolumns에서 잠금을 얻는다. 또한, 익스텐트(EXT) 잠금과 새 테이블에 있는 스키마 수정 잠금도 나타난다. 익스텐트 ID는 1:192형시으로 나타나느데 이것은 1번 파일에 있는 192번 페이지가 익스텐트의 첫번째 페이지라는 것을 의미하고, 테이블이 생성되고 있는 동안 이 익스텐트에 있는 7개 페이지(193-199)가 모두 단독 잠금 상태로 된다.

잠금 호환성
동일한 개체에 대해 한 잠금이 유지되고 있는 동안 다른 잠금이 허용될 수 있다면 이 두 잠금들은 호환된다. 반면, 개체에 대해 요청된 잠금이 현재 유지되고 있는 잠금과 호환되지 않으면, 요청한 연결이 잠금을 기다려야 한다. 예를 들어, 페이지에 공유 페이지 잠금이 존재한다면, 동일한 페이지에 대해 공유 페이지 잠금을 요청하는 다른 프로세스가 잠금을 얻을 수 있다. 이것은 두 잠금 형식이 호환되기 때문이다. 그러나 동일한 페이지에 대해 단독 잠금을 요청하는 프로세스는 잠금을 얻지 못할 것이다. 이것은 단독 잠금이 “이미 유지되고 있는 공유 잠금”과 호환되지 않기 때문이다.
SQL Server의 잠금호환성(O는 호환가능, X는 호환불가)
요청 모드 기존허용모드
 IS S U IX SIX X Sch-S Sch-M BU
IS O O O O O X O X X 
S O O O X X X O X X 
U O O X X X X O X X 
IX O X X X X X O X X 
SIX O X X X X X O X X 
X X X X X X X O X X 
Sch-S O O O O O O O X O 
Sch-M X X X X X X X X X 
BU X X X X X X O X O 
잠금 호환성은 서로 다른 리소스들의 잠금(예:테이블 잠금과 페이지 잠금) 사이에도 관여한다. 테이블이 여러 페이지들로 구성되어 있기 때문에 테이블과 페이지는 분명히 암시적으로 계층 구조를 갖는다. 테이블의 한 페이지에 단독 페이지 잠금이 유지되면 다른 프로세스가 이 테이블에 대해 공유 테이블 잠금조차 얻을 수 없다. 이 계층 구조는 내재된 잠금을 통해 보호된다. 단독 페이지 잠금이나 업데이트 페이지 잠금이나 내재된 단독 페이지 잠금을 얻고 있는 프로세스는 먼저 테이블에서 내재된 단독 잠금을 얻는다. 이 내재된 단독 테이블 잠금은 다른 프로세스들이 이 테이블에서 공유 테이블 잠금을 얻지 못하게 한다.(내재된 단독 잠금과 공유 잠금은 호환되지 않는다.)
이와 비슷하게 공유 행 잠금을 얻고 있는 프로세스는 먼저 테이블에 대해 내재된 공유잠금을 얻어야 한다. 이 내재된 공유 잠금은 다른 프로세스들이 단독 테이블 잠금을 얻지 못하게 방지한다. 또는, 단독 테이블이 잠금이 이미 존재하면 내재된 공유 잠금이 허용되지 않고, 단독 테이블 잠금이 해제될 때까지 공유 페이지 잠금이 기다려야 한다. 내재된 잠금이 없다면 A 프로세스가 테이블의 한 페이지를 단독 페이지 잠금으로 잠그고 B 프로세스가 동일한 테이블에서 단독 테이블 잠금을 얻은 후 테이블 전체(A프로세스가 단독으로 잠금 페이지 포함를 변경할 권한을 가졌다고 생각할지 모른다.
두 잠금이 호환되더라도, 호환되지 않은 잠금이 기다리고 있다면 두번째 잠ㄱ므을 요청한 프로세스가 여전히 기다려야 할지도 모른다. 예를 들어, A프로세스가 공유 페이지 잠금을 유지한다고 가정하자. 공유 페이지 잠금과 단독 페이지 잠금이 호환되지 않기 때문에 B 프로세스는 단독 페이지 잠금을 요청하고 기다려야한다. C 프로세스닌 이미 A프로세스가 유지하고 있는 공유 페이지 잠금과 호환되는 공유 페이지 잠금을 요청한다. 그러나 공유 페이지 잠금은 즉시 허용될 수 없다. 잠금 대기열에서 B프로세스가 C프로세스보다 앞에 있기 때문에 C프로세스는 공유 페이지 잠금을 기다려야 한다.

내부 잠금 아키테쳐
잠금은 디스크에 있는 구조가 아니다. 잠금이 디스크에 없는 것은 잠금 동작들을 위해 디스크 I/O 동작을 수행하는 것이 너무 느리기 때문이다. 데이터 페이지나 테이블 헤더에서 직접 잠금 필드를 찾을 수 없을 것이다. 잠금은 내부 메모리 구조이다. 이것은 SQL Server를 위해 사용된 메모리의 일부를 소비한다. 잠긴 각 데이터 리소스들은 데이터베이스와 잠금 형식과 잠긴 리소스를 기술하는 정보를 추적하기 위해 64바이트 메모리를 필료오 한다. 잠금을 유지하고 있는 각 프로세스들도 32바이트로 된 잠금 소유자 블록을 갖고 있어야 한다. 한 트랜잭션이 여러 개의 잠금 소유자 블록을 가질 수 있다. 또한, 한 잠금이 많은 잠금 소유자 블록을 가질 수 있다. 마지막으로, 잠금을 기다리고 있는 각 프로세스들은 32바이트로 된 잠금 대기자 블록을 갖는다. 잠금 소유자 블록과 잠금 대기자 블록이 동일한 구조를 갖기 때문에 필자는 “잠금 소유자 블록” 용어를 사용하여 이 두 가지를 모두 나타낼 것이다.

행 수준 잠금과 페이지 수준 잠금
행 수준 잠금이 페이지 수준 잠금보다 더 좋은지 아니면 페이지 수준 잠금이 더 좋은 논쟁하는 것은 오래된 논쟁 거리였다.
버전 7.0 이전에는 SQL Server가 잠글 수 있었던 가장 작은 데이터 단위가 페이지였다. 많은 사람들이 페이지 전체를 잠그는 동안 좋은 동시성을 유지할 수 없다고 주장했지만, 페이지 수준 잠금만을 사용하여 작성되고 배포된 많은 애플리케이션들이 있었다. 애플리케이션이 잘 설계되고 튜닝되었다면 동시성은 문제가 되지 않았고, 일부 애플리케이션들은 수백 개의 활성 사용자 연결을 지원 하면서도 좋은 응답 시간을 가지고 있었다. 그러나 SQL Server 7.0에서 페이지 크기가 2K에서 8KB로 바뀌면서 문제가 페이지 수준 잠금의 문제가 생기게 되었다. 페이지 전체를 잠그는 것은 이전 버전에서보다 4배의 데이터를 잠그는 것을 의미한다. SQL Server는 7.0부터 완전 행 수준 잠금을 구현했기 때문에, 더 큰페이지 크기에서 낮은 동시성으로 인해 발생할 수 있는 문제들은 실제로 문제가 되지 않아야 했다. 그러나 잠금은 자유가 아니다. 잠금을 관리하는 32바이트 구조이고, 잠금을 유지하고 있는 각 프로세스들과 잠금을 기다리고 있는 각 프로세스들마다 32바이트씩 필요하다는 것을 상기하기 바란다. 모든 행에 대해 잠금이 필요하고 백만개의 행을 스캔한다면, 이 한 개의 프로세스에서 잠금을 유지하기 위해 30MB 이사의 RAM이 필요하다.
메모리 사용 문제를 생각지 않더라도 잠금은 프로세스 집약적 동작이다. 잠금을 관리하려면 많은 것들을 기록해야 한다. 내부적으로 SQL Server는 스핀 잠금이라고 불리는 가변운 mutex를 사용하여 리소스를 보호하고, 래치를 사용하여 잎 수준이 아닌 인덱스 페이지들을 보호한다. 이것들을 사용하여 성능을 최적화함으로써 완전히 잠가야 하는 오버헤드를 피할 수 있다. 한 페이지에 50개의 행이 들어 있고 이것들이 모두 사용된다면, 50개를 관리하는 것보다 페이지에서 한 개의 잠금을 관리하는 것이 더 효과적일 것이다. 이것이 페이지 잠금의 이점이다.(즉, 만들어지고 관리되어야 하는 잠금 구조의 수가 줄어든다.)
두 프로세스가 각각 서로 다른 행들을 업데이트할 필요가 있고 이 행들 중의 일부가 우연히 동일한 페이지에 존재한다면, 한 프로세스의 페이지 잠금이 해제될때까지 다른 프로세스가 기다려야 한다. 이 경우에 여러분이 페이지 수준 잠금 대신 행 수준 잠금을 사용한다면 다른 프로세스가 기다릴 필요가 없다. 작은 잠금 단위를 사용하면 각 프로세스들이 서로 다른 행들을 사용하기 때문에 충돌이 일어나지 않는다. 이것은 행 수준 잠금의 이점이다. 이 장점들 중에서 어떤 것이 더 중요하겠는가? 이것은 간단히 결정할 수 있는 문제가 아니고 애플리케이션과 데이터에 따라 달라진다.
Sp_indexoption 저장프로시저는 인덱스 내에서 잠금 단위를 수동으로 제어할 수 있게 해준다. 또한, 이 프로시저는 인덱스 내에서 페이지 잠금이나 행 잠금을 허용하지 않을 수 있게 해준다. 인덱스에 대해서만 이 옵션을 사용할 수 있기 때문에 힙의 데이터 페이지 내에서 잠금을 제어하는 방법이 없다.(그러나, 테이블이 클러스터된 인덱스를 가지고 있다면 데이터 페이지는 인덱스의 일부이고 sp_indexoption설정에 의해 영향을 받는다.) 인덱스 옵션은 각 테이블이나 인덱스에 대해 개별적으로 설정된다. 두 가지 옵션 AllowRowLock와 AllowPageLock는 초기에 모든 테이블과 인덱스에 대해서 TRUE로 설정되어 있다. 테이블에 대해 이 옵션들이 모두 FALSE로 설정되면 완전 테이블 잠금만이 허용된다.
SQL Server는 런타임 시에 추기에 행을 잠글 것인지 페이지를 잠글 것인지 테이블 전체를 잠금 것인지를 결정한다. 행(또는, 키) 잠금이 주로 선호된다. 잠금 형식은 스캔될 행 및 페이지의 수와 페이지에 있는 행의 수와 사용중인 격리 수준과 진행중인 업데이트 동작과 시스템에서 메모리를 필요로 하는 사용자의 수에 근거하여 선택된다.
 잠금 수준 조정
SQL Server는 필요할 때 행 잠금이나 키 잠금이나 페이지 잠금을 자동으로 테이블 잠금으로 바꾼다. 이 잠금 수준 조정은 시스템 리소스를 보호하고(즉, 시스템이 잠금을 유지하기 위해 너무 많은 메모리를 사용하지 않도록 방지해주고), 효율성을 증대시킨다. 예를 들면, 쿼리가 많은 행 잠금을 얻은 후 잠금 수준은 테이블 잠금으로 바뀔 수 있다. 테이블에 있는 모든 행들이 방문되어야 한다면 아마도 많은 행 잠금들을 얻는 것보다 한 개의 테이블 잠금을 얻는 것이 더 나을 것이다. 한 개의 테이블 잠금이 얻어지고 많은 행 잠금들이 해제된다. 테이블 잠금으로 바뀌면 잠금 오버헤드가 줄어들고 시스템에서 잠금 부족 현상일 일어나지 않게 예방된다. 잠금 구조에 사용할 수 있는 메모리의 양이 유한이기 때문에 이따금 잠금에 사용되는 메모리가 일정한 한계 내에 머무르도록 하기 위해 잠금 수준 조정이 필요한다.
한 트랜잭션에 대한 잠금 카운트가 1250을 초과할 때 또는 한 인덱스나 테이블 스캔에 대한 잠금 카운트가 765를 초과할 때, 잠금 관리자는 시스템의 모든 잠금들을 위해 얼마나 많은 메모리가 사용되고 있는지 알아본다. 메모리 풀의 40%이상이 잠금을 위해 사용되고 있다면 SQL Server가 여러 개의 페이지 잠금이나 키 잠금이나 RID 잠금을 테이블 잠금으로 바꾸려고 시도한다. SQL Server는 트랜잭션에 의해 부분적으로 잠기고 가장 많은 잠금 수를 유지하고 있는 테이블을 찾으려고 한다. 다른 프로세스가 동일한 테이블에 있는 다른 RID나 키나 페이지에서 호환되지 않은 잠금을 유지하고 있다면 여러 RID 잠금이나 키 잠금이나 페이지 잠금들은 테이블 잠금으로 바뀔 수 없다 가능한 모든 잠금 수준 조정이 이루어지거나 잠금에 사용된 전체 메모리가 40%이하로 떨어질 때까지 SQL Server는 동일한 트랜잭션에 의해 부분적으로 잠긴 다른 테이블들을 계속해서 찾을 것이다. SQL Server는 결코 페이지 잠금으로 바꾸지 않는다는 것에 주목하기 바란다. 잠금 수준 조정의 결과는 항상 테이블 잠금이다.

AND

락 에스컬레이션

SQL Server 2007. 2. 27. 16:34
[잠금 수준 조정]
SQL Server
는 필요할 때 행 잠금이나 키 잠금이나 페이지 잠금을 자동으로 테이블 잠금으로 바꾼다. 이 잠금 수준 조정은 시스템 리소스를 보호하고(, 시스템이 잠금을 유지하기 위해 너무 많은 메모리를 사용하지 않도록 방지해주고), 효율성을 증대시킨다. 예를 들면, 쿼리가 많은 행 잠금을 얻은 후 잠금 수준은 테이블 잠금으로 바뀔 수 있다. 테이블에 있는 모든 행들이 방문되어야 한다면 아마도 많은 행 잠금들을 얻는 것보다 한 개의 테이블 잠금을 얻는 것이 더 나을 것이다. 한 개의 테이블 잠금이 얻어지고 많은 행 잠금들이 해제된다. 테이블 잠금으로 바뀌면 잠금 오버헤드가 줄어들고 시스템에서 잠금 부족 현상일 일어나지 않게 예방된다. 잠금 구조에 사용할 수 있는 메모리의 양이 유한이기 때문에 이따금 잠금에 사용되는 메모리가 일정한 한계 내에 머무르도록 하기 위해 잠금 수준 조정이 필요한다.
한 트랜잭션에 대한 잠금 카운트가 1250을 초과할 때 또는 한 인덱스나 테이블 스캔에 대한 잠금 카운트가 765를 초과할 때, 잠금 관리자는 시스템의 모든 잠금들을 위해 얼마나 많은 메모리가 사용되고 있는지 알아본다. 메모리 풀의 40%이상이 잠금을 위해 사용되고 있다면 SQL Server가 여러 개의 페이지 잠금이나 키 잠금이나 RID 잠금을 테이블 잠금으로 바꾸려고 시도한다. SQL Server는 트랜잭션에 의해 부분적으로 잠기고 가장 많은 잠금 수를 유지하고 있는 테이블을 찾으려고 한다. 다른 프로세스가 동일한 테이블에 있는 다른 RID나 키나 페이지에서 호환되지 않은 잠금을 유지하고 있다면 여러 RID 잠금이나 키 잠금이나 페이지 잠금들은 테이블 잠금으로 바뀔 수 없다 가능한 모든 잠금 수준 조정이 이루어지거나 잠금에 사용된 전체 메모리가 40%이하로 떨어질 때까지 SQL Server는 동일한 트랜잭션에 의해 부분적으로 잠긴 다른 테이블들을 계속해서 찾을 것이다. SQL Server는 결코 페이지 잠금으로 바꾸지 않는다는 것에 주목하기 바란다. 잠금 수준 조정의 결과는 항상 테이블 잠금이다.
AND

SQL 서버 온라인 도움말에 따르면 page life expectancy 카운터는 페이지가 참조 없이 버퍼 풀에 있어야 하는 초 수 라고 설명하고 있다. , page life expectancy 300초라면 해당 페이지에 대한 다른 참조작업이 없는 경우 버퍼 풀에서 디스크상으로 제거되기 전에 적어도 5분 동안은 버퍼 풀의 메모리상에 존재할 수 있는 상태를 의미한다.

Page life expectancy 카운터는 서버상에 메모리 압박 상황이 발생하고 있는지에 대해서 매우 정확한 보고를 해 주는 역할을 하기 때문에 현재 메모리상에 문제가 발생하고 있는지를 판단하는데 도움이 된다. 마이크로소프트에 따르면, page life expectancy 카운터의 값은 적어도 300초는 되어야 한다고 한다. 만약 버퍼 풀에서 페이지가 300초가 안되어 제거된다면 메모리상의 문제가 발생하고 있다고 할 수 있다. page life expectancy 카운터가 300초 이상인지 이하 인지만 단순하게 점검해 보면 되기 때문에 메모리 문제가 발생하고 있는지를 손쉽게 파악할 수 있다. 예를 들어 고객으로부터 메모리를 증설할 필요가 있나? 라는 질문을 받았는데 고객 시스템의 page life expectancy 카운터 값을 점검한 결과, 그 값이 300초 이하로 떨어지지 않는 경우라면 메모리를 아무리 증설 하더라도 성능에는 아무런 개선이 되지 않는다. 만약 다른 고객으로부터 동일한 질문을 받았는데 해당 시스템의 평균 page life expectancy 카운터 값이 10에서 50초라면 쿼리가 왜 그렇게 늦게 실행 되는지에 대해서 충분하게 이유를 설명해 줄 수 있을 것이다.

실제로 많은 회사에서 명확한 근거 없이 메모리를 추가하는 것은 성능향상에 그리 많은 도움이 되지 못하는 상황을 자주 볼 수 있다. page life expectancy 카운터 값이 지속적으로 낮은 상태로 나타나는 상황에서는 메모리를 추가하는 것이 제대로 된 해결책은 아니다. 메모리 압박현상은 대부분 비효율적인 쿼리계획 때문에 발생하게 된다. 예를 들어 page life expectancy 카운터 값이 50초 정도이고 서버에 150만 정도의 논리적 읽기 작업을 하는 select 쿼리를 실행하는 경우를 생각해 볼 수 있다. 또한 극심한 예로는 700MB의 메모리가 설치된 서버에서 11.5GB의 논리적 읽기작업을 수행하는 경우도 있다. 서버상에 메모리를 추가하기 보다는 합리적인 인덱스 전략을 수립하면 문제가 되는 논리적 읽기작업을 획기적으로 줄여줄 수 있으며 이 방법이 전체적인 성능향상에 더 도움이 될 수 있다.

메모리 압박이란 물리적인 메모리를 추가해야 한다는 것을 의미하는 것만은 아니다. 오히려 현재 작업부하를 처리하기에 충분한 메모리가 없다는 것을 의미한다고 할 수 있다. 그렇기 때문에 150만 논리적 읽기를 발생시키는 쿼리를 좀 더 합리적인 인덱스 전략을 기준으로 튜닝 하게 되면 충분한 메모리의 가용량을 확보 할 수 있다. page life expectancy 카운터가 높은 수치로 나타나는 상황에서는 메모리를 증설한다고 해도 성능상에 개선이 되지 않는다는 사실을 명확하게 알아둘 필요가 있다.

AND