BLOG main image
분류 전체보기 (45)
SQL Server 이야기 (45)
일상 (0)
Visitors up to today!
Today hit, Yesterday hit
daisy rss
tistory 티스토리 가입하기!
'SQL Server 이야기'에 해당되는 글 45건
2009. 7. 28. 22:36

SQL Server는 호출되는 SP를 최초 매개변수를 기반으로 컴파일 하여, 플랜 캐시에 저장합니다.
플랜 캐시에 저장된 내용을 바탕으로 이후 SP가 호출되면 다시 컴파일 하지 않고 재사용하게 되어

전체적인 성능 향상에 도움을 주고 있습니다
.
하지만 파라미터 스니핑으로 인해 전체적인 성능에 나쁜 영향을 줄 수 있습니다


갑자기 멀쩡한 서버가 각각의 성능 수치가 급격히 증가되었다면,
파라미터 스니핑으로
잘못된 실행계획으로 처리되는 것이 원인일 수 있습니다.


이때 문제가 되는 SP는 프로파일러 또는 DMV를 통해 쉽게 확인할 수 있으나,

과연 어떠한 파라미터로 인해 잘못된 실행계획으로 풀려 문제가 되는지는 확인하기가 쉽지 않습니다.

   

XML 플랜을 사용하면 이러한 질문에 대한 대답을 얻을 수 있습니다.

XML 플랜에 아래와 같은 데이터가 있으며, 이 값을 XQUERY로 읽어오면 ~ 보다 쉽게

컴파일 때 사용한 매개변수 값을 확인 할 수 있습니다
 

<ParameterList> 
              <ColumnReference Column="@a" ParameterCompiledValue="(1)" /> 
              <ColumnReference Column="@b" ParameterCompiledValue="(100)" />
</
ParameterList>


SELECT

           db_name,sp_name,[statement_text]

           ,[Column]

           ,ParameterCompiledValue

           ,[creation_time]

           ,[ExecutionCount]

           ,[query_plan]

          

FROM (

           SELECT

                     db_name(qt.dbid) AS 'db_name'

                     , substring(qt.text, (qs.statement_start_offset/2)+1

                                         , ((case qs.statement_end_offset

                                            when -1 then datalength(qt.text)

                                            else qs.statement_end_offset

                                           end - qs.statement_start_offset)/2) + 1) as statement_text

                     , qs.creation_time

                     , qs.execution_count AS 'ExecutionCount'

                     , ISNULL(qs.execution_count/DATEDIFF(Second, qs.creation_time, GetDate()), 0) AS 'Calls/Second'

                     , DATEDIFF(Minute, qs.creation_time, GetDate()) AS 'Age in Cache(min)'

                     , cast(qp.query_plan as xml) as query_plan

                     ,OBJECT_NAME(qp.objectid,qp.dbid) as sp_name

                                FROM sys.dm_exec_query_stats AS qs

                                          CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt

                                          CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle,qs.statement_start_offset,qs.statement_end_offset) qp

           )X OUTER APPLY

                           (

                     SELECT

                                c.value('(@Column)[1]','varchar(100)') AS [Column]

                                ,c.value('(@ParameterCompiledValue)[1]','varchar(1000)') AS ParameterCompiledValue

                     FROM query_plan.nodes('

                     declare default element namespace "http://schemas.microsoft.com/sqlserver/2004/07/showplan";//ParameterList/ColumnReference')B(C)

                     )xp


송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com
2009. 7. 27. 22:07

SQL Server 2008의 새로운 기능 중에 하나인 optimize for ad hoc workloads 옵션은 출시 전부터 많은 관심을 받던 기능이었습니다.
SQL Server 2008이전 버전에서는 ad-hoc쿼리가 자주 사용되는 환경에서는 플랜 캐시 사이즈가 계속 커지는 문제가 발생하여
전체적인 메모리 사용에 비효율을 가져왔습니다. 
 

위와 같은 문제에 대해서 어느 정도 해결해 줄 수 있는 옵션이 optimize for ad hoc workloads 입니다.
이 옵션이 하는 역할은 간단하게 보면 처음으로 실행되는 ad-hoc쿼리가 실행되면 320바이트의 stub영역에 데이터를 저장합니다.
동일한 ad-hoc쿼리가 두 번째 다시 실행되면 그때 일반적인 플랜캐시에 저장하도록 합니다. 

한번밖에 호출되는 ad-hoc쿼리의 비중이 많은 환경이라면, 전체적인 메모리를 꽤나 절약할 수 있을 것입니다. 
 

아래는 BOL에서 설명하고 있는 내용입니다.

optimize for ad hoc workloads 옵션

optimize for ad hoc workloads 옵션은 여러 개의 일회용 임시 일괄 처리를 포함하는 작업에서 계획 캐시의 효율성을 높이는 데 사용됩니다.

이 옵션을 1로 설정하면 데이터베이스 엔진이 일괄 처리가 처음으로 컴파일되었을 때 전체 컴파일된 계획 대신 계획 캐시에 포함된 작은 컴파일된 계획 스텁을 저장합니다.

이렇게 하면 계획 캐시에 다시 사용할 수 없는 컴파일된 계획이 채워지지 않게 되므로 메모리 가중을 줄일 수 있습니다.

컴파일된 계획 스텁은 데이터베이스 엔진이 이러한 임시 일괄 처리가 이전에 컴파일되었지만 컴파일된 계획 스텁만 저장했다는 것을 인식하게 함으로써

이 일괄 처리(컴파일 또는 실행된)가 다시 호출될 때 데이터베이스 엔진이 일괄 처리를 컴파일하고, 계획 캐시에서 컴파일된 계획 스텁을 제거하고, 계획 캐시에 전체 컴파일된 계획을 추가하도록 합니다.

optimize for ad hoc workloads를 1로 설정하면 새 계획만 영향을 받으며, 이미 계획 캐시에 있던 계획은 영향을 받지 않습니다.

컴파일된 계획 스텁은 sys.dm_exec_cached_plans 카탈로그 뷰로 표시되는 cacheobjtype 중 하나입니다.

여기에는 고유한 SQL 핸들 및 계획 핸들이 포함됩니다. 컴파일된 계획 스텁에는 연결된 실행 계획이 없으며 계획 핸들을 쿼리해도 XML 실행 계획이 반환되지 않습니다.


간단하게 테스트를 하였습니다. 환경은 SQL Server 2008 with sp1입니다.

ad-hoc쿼리를 999번 수행하여 플랜 캐시의 메모리를 비교하였습니다.
간단히 결론만 알아보면 옵션을 사용하지 않은 경우 플랜 캐시 메모리는 40MB정도, 사용한 경우에는 1MB정도 입니다. 
 

자세한 내용은 아래를 참조하세요.

     

/*
ad-hoc쿼리를 999번 실행합니다.
*/
declare
@a int = 1
,@s varchar(1000)
while @a < 1000
BEGIN
SET
@s = 'SELECT TOP 1 col1,count(*) FROM dbo.tbl WITH(NOLOCK) 
    WHERE col1 = '
+str(@a)+
    GROUP BY col1' 
exec (@s)
set @a = @a + 1
end

  ===============================================================================[ad-hoc쿼리 최적화 아닐때]

/*
옵션을 비활성화 합니다.
*/

DBCC
FREEPROCCACHE
GO
exec
sp_configure 'optimize for ad hoc workloads',0
reconfigure with override

 exec sp_configure 'optimize for ad hoc workloads'

 /*
DMV를 통해 확인해 보면,
쿼리당 40KB정도 차지하고 있는 것을 볼 수 있습니다.
*/

SELECT 
    qt.text 
    ,cp.size_in_bytes 
    ,cp.cacheobjtype 
    ,cp.objtype
from sys.dm_exec_cached_plans cp
join sys.dm_os_memory_cache_entries ce
on cp.memory_object_address=ce.memory_object_address
cross apply sys.dm_exec_sql_text(cp.plan_handle) as qt    

 SELECT * FROM sys.dm_os_memory_clerks
WHERE type like '%cp%'

 --===============================================================================[ad-hoc쿼리 최적화]

/*
옵션을 활성화 합니다.
*/

DBCC
FREEPROCCACHE
GO
exec
sp_configure 'optimize for ad hoc workloads',1
reconfigure with override 

exec
sp_configure 'optimize for ad hoc workloads'

  /*
DMV를 통해 확인해 보면,
쿼리당 320 바이트 정도 차지하고 있는 것을 볼 수 있습니다.
*/

SELECT 
    
qt.text 
    
,cp.size_in_bytes 
    
,cp.cacheobjtype 
    
,cp.objtype
from
sys.dm_exec_cached_plans cp
join sys.dm_os_memory_cache_entries ce
on cp.memory_object_address=ce.memory_object_address
cross apply sys.dm_exec_sql_text(cp.plan_handle) as qt   

 
     

SELECT * FROM sys.dm_os_memory_clerks
WHERE type like '%cp%'

 

/*
그럼 다시 Ad-hoc쿼리를 다시 한번 수행하면?
설명대로 stub에 저장된 것들이 사라지고 모두 컴파일 되서 플랜 캐시에 저장되는 것을 볼 수 있습니다. 
*/

declare @a int = 1
,@s varchar(1000)
while @a < 1000
BEGIN
SET
@s = 'SELECT TOP 1 col1,count(*) FROM dbo.tbl WITH(NOLOCK) 
    WHERE col1 = '
+str(@a)+' 
    GROUP BY col1' 
exec (@s)
set
@a = @a + 1
end 
 

/*
DMV를 통해 확인해 보면,
쿼리당 40KB정도 차지하고 있는 것을 볼 수 있습니다.
*/

SELECT 
    
qt.text 
    
,cp.size_in_bytes 
    
,cp.cacheobjtype 
    
,cp.objtype
from
sys.dm_exec_cached_plans cp
join sys.dm_os_memory_cache_entries ce
on cp.memory_object_address=ce.memory_object_address
cross apply sys.dm_exec_sql_text(cp.plan_handle) as qt   

 

송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com

2009. 7. 6. 16:06

 

얼마 전 IODrive를 지엔오테크롤로지의 협찬을 받아서 일주일 동안 IO관련 테스트를 진행하였습니다.

서버용 제품으로, 벤더에서 제공하는 스펙만 본다면 기존에 테스트 했던 버텍스 또는 엠트론 보다 월등히 뛰어난 IOPS 수치를 보여주고 있습니다. 또한 특이한 것은 다른 SSD와 다르게 SATA 인터페이스가 아닌 PCI-E 인터페이스를 채택 하였습니다.
역시 서버용 제품이라, 가격은 생각보다 비쌌지만 서버에서 사용하는 엔터프라이즈 SAS디스크의 가격도 수십 만원에서 수백 만원 정도하기에, 적은 공간에 아주 많은 IOPS를 요구하는 비즈니스라면 일반 DISK를 사용하는 것 보다 적은 비용으로 구현 할 수 있을 것 입니다.  

이번에 테스트한 제품은 IODrive 80GB 입니다.

스펙 상으로는 읽기, 쓰기에 대략 10만 IOPS을 제공해 준다고 하며, 최대 bandwidth는 읽기 700MB/s, 쓰기는 550MB/s 을 정도 입니다.
이 정도의 Bandwidth는 일반 서버용 DISK 몇 장만 RAID를 구성하면 충분히 뽑아 낼 수 있기에 비싼 가격과 적은 용량을 가진 SSD만의 작업이라고 볼 수 없습니다.

하지만 랜덤 IOPS에서는 이야기가 달라집니다.
보통 15K rpm 엔터프라이즈 SAS 인터페이스 DISK도 300~400정도의 IOPS를 보여주고 있기에, IODrive가 제공하는 IOPS만큼을 디스크로 구성 하기 위해서는 대략 250장 정의 DISK가 필요하게 됩니다. 이렇게 비교해 보면 SSD의 랜덤 IOPS가 얼마나 많은 IOPS를 제공해 주는지 알 수 있습니다.

[ioDrive 스펙]

 

[IODrive 모습]

 

  • 테스트 시작

처음 IO테스트를 진행할 때 80GB제품 두 개를 RAID0으로 묶어서 테스트할 예정이었으나, 하드웨어 문제가 있어 부득이하게 싱글로 테스트를 진행하였습니다.

 간단한 테스트한 머신 스펙은 아래와 같습니다.

  • CPU : Intel Xeon E54xx * 2 (Quad-Core)
  • MEMORY: 2GB

 

테스트는 총 4가지로 테스트 하였습니다.

  1. SQLIO
  2. IOMeter
  3. SQL Server – Index Scan vs Index Lookup
  4. SQL Server – 물리적 조각화에 따른 SCAN성능 비교

 

  • 테스트 1 : SQLIO

    SQLIO는 단일형태의 IOPS를 산정할 수 있는 도구 입니다.
    SQL Server에서 주로 사용되는 8KB랜덤, 256KB 순차 읽기/쓰기로 테스트를 진행하였습니다. 간단히 결과를 알아본다면 랜덤 읽기의 경우 대략 65,000정도 / 쓰기 46,000정도의 IOPS를 보여주고 있습니다. 대략 초당 전송속도(Bandwidth)로 환산해 본다면 대략 500MB정도입니다.

     이 정도의 수치라면 기존 엔터프라이즈 디스크와 비교하면 읽기의 경우 대략 220배 정도 쓰기의 경우 150배 정도 많은 IOPS를 제공해 주고 있습니다.
    아래 표에서는 보여지지 않지만 대부분의 IO응답시간은 1ms이하로 매우 빠른 속도를 보여 주었습니다.
    혹시 위 벤더에서 제공하는 스펙과 차이가 많이 난다고 생각하신다면, 위 테스트는 4K단위로 테스트 하였고, 여기서는 8K단위 테스트 했기 때문입니다.


 

  • 테스트 2 : IOMETER

    SQL Server를 사용하는 서비스에 대해 IO-Sub System에 대한 성능 측정을 해야 한다면,SQL Server를 기반으로 테스트를 진행하면 가장 효과적이고 정확한 테스트 결과를 볼 수 있을 것입니다. 하지만 SQL Server 의 IO패턴을 자유자재로 조작할 수 없기에 이렇게 테스트 쉬운 작업이 아닐 것입니다.  

    이번에 테스트하는 IOMETER는 여러 가지 IO패턴을 만들 수 있는 기능이 있어 SQL Server의 IO패턴을 비슷하게 흉내 낼 수 있습니다. 그래서 이번에는 SQL Server의 IO패턴을 비슷하게 구현하여 IO 성능 테스트를 진행하였습니다.  

    우선 테스트를 하기 전 SQL Server의 IO패턴에 대해서 알아볼 필요가 있습니다.
    SQL Server의 IO패턴은 트랜잭션 로그를 제외하면 IO 패턴은 크게 두 가지로 볼 수 있습니다.

    • Buffer Pool(이하 BP)에 없는 데이터를 읽기 위한 Read IO
    • BP에서 디스크로 플러시 되지 않은 Dirty page를 디스크로 쓰는 checkpoint프로세스

     [디스크에서 분당 만 페이지씩 쓰기 작업]

우선 디스크에서 테스트한 결과를 살펴보면,

테스트 환경은 15K SAS 디스크 10장 (RAID10) 구성되었으며, 분당 만 페이지씩 쓰기 IO가 발생하게 됩니다. 간단히 그래프를 살펴보면 평균 읽기는 대략 3700쯤 되다가 쓰기IO가 수행되면 읽기 IO가 대략 2000정도 까지 떨어지는 현상을 보입니다. 쓰기 캐시가 장착된 모델이라 쓰기 시점에 쓰기 응답시간은 매우 빠르나 읽기 응답시간은 느려지는 것을 확인 할 수 있습니다.    

     [SSD에서 10초당 만 페이지씩 쓰기 작업]
   

SSD 에서의 테스트의 경우 위에서 테스트한 SQLIO로 생각 이상의 수치를 보여주었기에, 위 디스크와 다르게 10초당 만 페이지씩 쓰기 작업을 하였습니다.  
간단히 그래프를 살펴보면 평균읽기는 42000정도 되다가 쓰기IO가 수행되면, 읽기IO가 대략 33000정도 까지 떨어지는 현상을 보입니다.
PCI-E 인터페이스 이고 제품에도 따로 Write Back 캐시 역할을 해 줄 수 있는 것이 없기에, 쓰기IO 작업 시 위와 디스크에서 테스트한 결과와 다르게 쓰기 응답시간이 느려지는 것을 볼 수 있습니다. 하지만 느려져도 1ms정도이기에 문제가 있는 수치라고 볼 수 없습니다. 또 하나 위 디스크와 다른 부분은 읽기 응답시간입니다. 쓰기 IO가 발생해도 꾸준히 대략 0.4ms 이하 수준을 보여주고 있습니다.

     

  • 테스트 3 : SQL Server – Index Scan vs Index Lookup

 대량의 데이터를 읽어야 하는 경우라면, 인덱스를 룩업하는 것보다 인덱스를 스캔하는 것이 빠르며, 그 속도 차이는 수배에서 수십 배 빨랐습니다. SSD는 위 테스트에서 살펴보았듯이, 많은 랜덤 IOPS를 제공해 주고 있으며, 응답시간 또한 기존디스크에서 볼 수 없는 속도를 내주게 됩니다. 그렇다면 SSD환경에서 모든 데이터를 읽어야 하는 경우에 룩업으로 처리시 어느 정도 성능을 보여줄지 궁금합니다.  

아래 테스트는 7GB정도의 데이터를 입력 후 인덱스 힌트를 사용하여 인덱스 스캔과 인덱스 룩업에 대해 응답시간을 비교하였습니다.  

간단히 결론을 알아 본다면 SCAN작업은 10초, 룩업 작업은 15초 정도에 끝났습니다.

역시나 SCAN이 Lookup보다 빨랐습니다. 하지만 시간이 1.5배 정도밖에 차이가 나지 않은 점을 주목해야 할 것 입니다.  

SELECT COUNT(*) FROM TBL with(index(1),nolock)
/*
테이블'tbl'. 검색수9, 논리적읽기수1002491, 물리적읽기수14078, 미리읽기수1002490, LOB 논리적읽기수0, LOB 물리적읽기수0, LOB 미리읽기수0. 
SQL Server 실행시간:

CPU 시간= 2485밀리초, 경과시간= 10404밀리초
*/

 

SELECT COUNT(col2) FROM tbl WITH(INDEX(2),nolock)
/*
테이블'tbl'. 검색수9, 논리적읽기수12043916, 물리적읽기수2790, 미리읽기수1006198, LOB 논리적읽기수0, LOB 물리적읽기수0, LOB 미리읽기수0.
테이블'Worktable'. 검색수0, 논리적읽기수0, 물리적읽기수0, 미리읽기수0, LOB 논리적읽기수0, LOB 물리적읽기수0, LOB 미리읽기수0.

CPU 시간= 31797밀리초, 경과시간= 15898밀리초
*/

 

  • 테스트 4 : SQL Server – 물리적 조각화에 따른 SCAN성능 비교

    예전에 테스트한 물리적 조각화에 따라 SCAN 성능이 SSD환경에서는 어떨지 궁금해서 테스트 하였습니다. SSD는 기존의 디스크와 다르게 어떠한 데이터를 읽더라도 모두 동일한 시간에 읽을 수 있습니다. 하지만 기존 DISK는 물리적인 회전을 통해 데이터를 읽어야 하기에 데이터가 쓰여진 위치에 따라 응답시간이 차이가 발생하였습니다.
    그래서 같은 파일을 인접한 곳에 저장하는 조각모음이 필요했었습니다.
    하지만 SSD는 데이터가 어디에 있든 모두 동일한 시간으로 데이터를 가져올 수 있기에 따로 조각모음이 필요  없다고 합니다.

    디스크 환경에서 테스트한 스크립트를 이용하여 SSD에 동일하게 테스트를 진행하였습니다. 아래 수치를 보면 SSD는 물리적 조각화에 의해 전혀 성능적 영향을 받지 않았다고 볼 수 있습니다.
    어느 정도의 차이는 있지만, 테스트 환경에 따라 이 정도의 오차는 충분히 무시할 수 있는 수치로 보여집니다.
    SSD를 사용하면 더 이상 물리적 조각화로 인해 조각모음은 필요 없어 보입니다.

    뭐. SSD에 물리적 조각화라는 말이 안 어울리죠?

 

 

USE [master]
GO
CREATE DATABASE [TEST] ON PRIMARY
( NAME = N'TEST', FILENAME = N'F:\DBData\TEST.mdf' , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
LOG ON ( NAME = N'TEST_log', FILENAME = N'D:\DBlog\TEST_log.ldf' , MAXSIZE = 2048GB , FILEGROWTH = 10%)

CREATE DATABASE [TEST1] ON PRIMARY
( NAME = N'TEST1', FILENAME = N'F:\DBData\TEST1.mdf' , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
LOG ON ( NAME = N'TEST1_log', FILENAME = N'D:\DBlog\TEST1_log.ldf' , MAXSIZE = 2048GB , FILEGROWTH = 10%)

CREATE DATABASE [TEST2] ON PRIMARY
( NAME = N'TEST2', FILENAME = N'F:\DBData\TEST2.mdf' ,SIZE=5GB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
LOG ON ( NAME = N'TEST2_log', FILENAME = N'D:\DBlog\TEST2_log.ldf' , SIZE = 5GB , MAXSIZE = 2048GB , FILEGROWTH = 10%)

DECLARE @I VARCHAR(100)
SET @I= '4'
WHILE 1=1
BEGIN 
    EXEC ('ALTER DATABASE TEST MODIFY FILE(NAME = ''TEST'',SIZE='+@I+')'
    EXEC ('ALTER DATABASE TEST1 MODIFY FILE(NAME = ''TEST1'',SIZE='+@I+')')

     SET @I=@I+1
IF @I=5000 BREAK
END

/*
테스트를위해4000만건대략5GB 정도의데이터를입력한다.
*/

SELECT TOP 40000000
row_number() over(order by (select 1)) as col1
,cast('' as char(100)) col2
INTO test..tbl
FROM master.dbo.spt_values A,master.dbo.spt_values A1,master.dbo.spt_values A2,master.dbo.spt_values A3

SELECT TOP 40000000
row_number() over(order by (select 1)) as col1
,cast('' as char(100)) col2
INTO test2..tbl
FROM master.dbo.spt_values A,master.dbo.spt_values A1,master.dbo.spt_values A2,master.dbo.spt_values A3

exec sp_msforeachtable 'checkpoint 1'
dbcc dropcleanbuffers

SET STATISTICS TIME ON
SET STATISTICS IO ON

-- 조각화 상태
SELECT COUNT(*) FROM test.dbo.tbl
테이블 'tbl'. 검색 9, 논리적 읽기 579712, 물리적 읽기 7258, 미리 읽기 579711, LOB 논리적 읽기 0, LOB 물리적 읽기 0, LOB 미리 읽기 0.
CPU 시간 = 10359밀리초, 경과 시간 = 6005밀리초

 

-- 조각화 상태 아님
SELECT COUNT(*) FROM test2.dbo.tbl
테이블 'tbl'. 검색 9, 논리적 읽기 579712, 물리적 읽기 7190, 미리 읽기 579711, LOB 논리적 읽기 0, LOB 물리적 읽기 0, LOB 미리 읽기 0.
CPU 시간 = 9891밀리초, 경과 시간 = 5991밀리초

SET STATISTICS TIME OFF
SET STATISTICS IO OFF

송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com

2009. 6. 25. 10:44

SQL Server 2005 Standard Edition X64환경에 CU4 설치 및 시작 추적 플래그 845 설정 하게 되면
아래와 같이 Lock Pages in memory 정책을 지원하는 Using Locked pages for buffer pool 이라는
메시지를 확인 할 수 있습니다.


혹시 x64환경에서 SQL Server 2005 Standard Edition을 사용하신다고
Working Set 이 Trim되는 현상이 있다면 테스트 후 반듯이 CU4를 적용하시는 편이 좋습니다.



송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com
2009. 6. 25. 00:13

SQL Server 2005부터 추가된 DMV를 통해 여러 가지 유용한 스크립트를 만들 수 있습니다.

이번에는 특정 DB 또는 모든 DB에 대해 SELECT, DELETE, INSERT 또는 UPDATE Statmemt가
어떠한 비율로 발생하는지 그리고 리소스별 어떠한 비율로 사용하는지 쉽게 확인 할 수 있는 쿼리 입니다.

지금까지 막연하게 "이 DB는 대부분이 SELECT가 발생하는 것이야."
이러한 생각을 구체적이고 정확하게 보여줄 수 있습니다.

처음 접하는 시스템을 분석시 위와 같은 자료가 있으면 보다 쉽게 파악 할 수 있을 것입니다.
제 생각에는 ~ 꽤 DB관련 업무에 유용하게 사용될 수 있을 것 같습니다.ㅎㅎ

아래는 수행한 결과 입니다.
ExecutionCount는 분당 실행한 횟수 입니다.

[로그성 DB시스템]  

[일반 OLTP DB시스템]

with XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' as sql)

SELECT

       StatementType

       ,ExecutionCount

       ,cast((ExecutionCount*1.0/total_ExecutionCount)*100 as Decimal(18,2)) as [% ExecutionCount]

       ,cast((Elapsed_Time/total_Elapsed_Time)*100 as Decimal(18,2)) as [% Elapsed_Time]

       ,cast((Worker_Time/total_Worker_Time)*100 as Decimal(18,2)) as [% Worker_Time]

       ,cast((logical_reads/total_logical_reads)*100 as Decimal(18,2)) as [% logical_reads]

      ,cast((logical_writes/total_logical_writes)*100 as Decimal(18,2)) as [% logical_writes]

      ,cast((physical_reads/total_physical_reads)*100 as Decimal(18,2)) as [% physical_reads]

FROM

(

       SELECT

             StatementType

             ,sum(avg_ExecutionCount) over(partition by StatementType) as ExecutionCount

             ,sum(avg_ExecutionCount) over(partition by 1) as total_ExecutionCount

             ,sum(avg_total_Elapsed_Time) over(partition by StatementType) as Elapsed_Time

             ,sum(avg_total_Elapsed_Time) over(partition by 1) as total_Elapsed_Time

             ,sum(avg_total_Worker_Time) over(partition by StatementType) as Worker_Time

             ,sum(avg_total_Worker_Time) over(partition by 1) as total_Worker_Time

             ,sum(avg_total_logical_reads) over(partition by StatementType) as logical_reads

             ,sum(avg_total_logical_reads) over(partition by 1) as total_logical_reads

             ,sum(avg_total_logical_writes) over(partition by StatementType) as logical_writes

             ,sum(avg_total_logical_writes) over(partition by 1) as total_logical_writes

             ,sum(avg_total_physical_reads) over(partition by StatementType) as physical_reads

             ,case when

                    sum(avg_total_physical_reads) over(partition by 1) = 0

                           THEN NULL ELSE sum(avg_total_physical_reads) over(partition by 1)

             END    as total_physical_reads

       FROM(

             SELECT StatementType

                    ,count(*) as cnt

                    ,sum(ISNULL(ExecutionCount/DATEDIFF(MINUTE , creation_time,GetDate()),0)) as avg_ExecutionCount

                    ,cast(sum(ISNULL([total_Elapsed_Time(sec)]/DATEDIFF(MINUTE , creation_time, GetDate()), 0)) as Decimal(18,2)) as avg_total_Elapsed_Time

                    ,cast(sum(ISNULL([total_Worker_Time(ms)]/DATEDIFF(MINUTE , creation_time, GetDate()), 0)) as Decimal(18,2)) as avg_total_Worker_Time

                    ,cast(sum(ISNULL([total_logical_reads]/DATEDIFF(MINUTE , creation_time, GetDate()), 0)) as Decimal(18,2)) as avg_total_logical_reads

                    ,cast(sum(ISNULL([total_logical_writes]/DATEDIFF(MINUTE , creation_time, GetDate()), 0)) as Decimal(18,2)) as avg_total_logical_writes

                    ,cast(sum(ISNULL(total_physical_reads/DATEDIFF(MINUTE , creation_time, GetDate()), 0)) as Decimal(18,2)) as avg_total_physical_reads

             FROM (

                           SELECT

                                 db_name

                                 ,ISNULL([StatementType],StatementType2) as StatementType

                                 --,[statement_text]

                                 ,[creation_time]

                                 ,[ExecutionCount]

                                 ,[total_Elapsed_Time(sec)]

                                 ,[total_Worker_Time(ms)]

                                 ,[total_logical_reads]

                                 ,[total_logical_writes]

                                 ,[total_physical_reads]

                           FROM   (

                                              SELECT

                                                     db_name(qp.dbid) AS 'db_name'

                                                     , qs.creation_time

                                                     , qs.execution_count AS 'ExecutionCount'

                                                     , qs.total_elapsed_time/1000.0/1000.0 AS 'total_Elapsed_Time(sec)'

                                                     , qs.total_worker_time/1000.0 AS 'total_Worker_Time(ms)'

                                                     , total_logical_reads

                                                     , qs.total_logical_writes

                                                     , total_physical_reads

                                                     , cast(qp.query_plan as xml) as query_plan

                                              FROM sys.dm_exec_query_stats AS qs

                                                     CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle,qs.statement_start_offset,qs.statement_end_offset) qp

                                        )X

                           OUTER APPLY

                                 (

                                 SELECT

                                        c.value('(//sql:StmtSimple/@StatementType)[1]','VARCHAR(100)') AS "StatementType"

                                        ,c.value('(//sql:StmtCond/@StatementType)[1]','VARCHAR(100)') AS "StatementType2"

                                 FROM query_plan.nodes('//sql:QueryPlan')B(C)

                                 )xp

                    WHERE db_id(db_name) > 4

                    AND DATEDIFF(MINUTE,creation_time, GetDate()) > 0

                    AND StatementType is not null

             )X

             group by StatementType

       )Y

)Z



송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com

2009. 6. 16. 23:20

어제 SQL Server 2005 SP3의 CU4가 발표되었습니다.
CU4관련 KB에는 Locked Page in Memory가 지원된다는 말이 없는데요~
다른 KB에서 보면 CU4에서 지원된다고 합니다.

바로 테스트 해볼 수 있는 환경은 없어서 직접 테스트는 해보지 못했습니다.
혹시 테스트 가능하시면 ~ 결과 좀 공유 해주세요~

자세한 내용은 아래 KB를 참조하세요.

[Cumulative update package 4 for SQL Server 2005 Service Pack 3]
http://support.microsoft.com/kb/970279/en

[How to reduce paging of buffer pool memory in the 64-bit version of SQL Server]
http://support.microsoft.com/kb/918483/en

> 위 KB에 CU4가 지원된다는 설명
SQL Server 2008 SP1 Cumulative Update 2 and SQL Server 2005 SP3 Cumulative Update 4 introduces support
for SQL Server Standard editions to use the Lock pages in memory user right.


송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com

2009. 5. 20. 10:17

SQL Server 2008 Standard x64에서 Locked Page in Memory를 지원하는 누적 패치가 발표되었습니다.
시작 Trace Flage에 845를 설정하면 사용할 수 있다고 합니다.

다음 달이면 SQL Server 2005에 대한 패치도 제공된다고 하니~ 기대 됩니다.

자세한 내용은 아래 링크를 참조하세요~

[Support for Locked Pages on SQL Server 2008 Standard Edition 64-bit systems]
http://support.microsoft.com/kb/970070

 

송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com

2009. 4. 28. 16:38

업무용으로 SLC SSD를 사용하다 기존 디스크를 다시 사용하니 너무 느리게 느껴져 요즘 한참 SSD쪽에서 이슈가 되고 있는
OCZ사의 Vertex를 구매하였습니다. OS 부팅 등 전체적인 체감 속도는 이전에 사용하던 SSD보다 좋아 졌습니다.

관련하여 기존에 테스트한 SLC SSD와 어느 정도 성능 차이가 있는지 IOPS 테스트를 진행하였으며 재미있는 결과를 얻었습니다.

기존에 테스트한 SLD SSD는 8KB 랜덤 쓰기 작업이 100 조차 되지 않아 SQL Server 에서 사용하기에는 꽤 무리가 있어 보였는데
이번에 테스트한 Vertex 제품의 경우 8KB 랜덤 쓰기 작업이 1600정도로 일반 엔터프라이즈용 15Krpm 디스크 보다 4배 이상
좋은 성능을 가지는 것을 확인 하였습니다.

또한 OCZ에서 서버용으로 SLC 기반의 SSD를 발표하였고, IBM 서버 제품 군에는 내부 디스크로 INTEL SSD를 장착할 수 있기에 
적은 용량에 대단히 많은 IOPS를 요구하는 서비스라면 충분히 고려해 볼 수 있을 것입니다.

이번에도 테스트는 총 4가지로 하였습니다.
- 랜덤 8KB 읽기 (thread 2 / outstanding 4)
- 랜덤 8KB 쓰기 (thread 2 / outstanding 4)
- 순차 1024KB 읽기 (thread 2 / outstanding 4)
- 순차 1024KB 쓰기 (thread 2 / outstanding 4)

[IOPS]

[MB/s]

송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com

2009. 4. 27. 10:45

CSS SQL Server Engineers 블로그에 좋은 뉴스가 떳습니다.

working set 이 trim이 되는 현상을 원천적으로 방지 할 수 있는"lock pages in memory" 정책은
기존 Enterprise Edition 에서만 지원되어  Standard Edition x64에서 운영 중에
Working Set이 Trim되어 서비스에 문제가 발생하기도 하였습니다.

하지만 이후 누적픽스 발표후에는 "lock pages in memory" 정책이 Standard Edition에서도 지원된다고 하며, 지원 방식은 이후 누적 픽스 설치 후 Trace Flag를 적용하면 된다고 합니다.

CU2 for SQL Server 2008 SP1 – Currently scheduled for May 2009

CU4 for SQL Server 2005 SP3 – Currently scheduled for June 2009

자세한 내용은 아래의 블로그를 참조하세요.
http://blogs.msdn.com/psssql/archive/2009/04/24/sql-server-locked-pages-and-standard-sku.aspx

송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com

2009. 4. 17. 11:52

쿼리에 인덱스 힌트를 사용하게 되면, 힌트에 사용되는 인덱스에 대해 이름 변경 또는 삭제와 같은 작업이 발생하게 되면
쿼리가 실행하지 못하고 에러가 발생되어 서비스에 문제를 줄 수 있습니다.

이러한 점으로 인덱스 힌트를 사용하는 환경이라면 인덱스 작업시 많은 주의가 필요합니다.

SQL Server 2000 버젼의 경우라면 이러한 인덱스 힌트를 사용한 SP를 확인하기 위해서는 syscomments 시스템테이블에서 확인 할 수 밖에 없었습니다.
이방법도 문자열을 비교하는 형태로 찾을 수 밖에 없기에 꽤나 시간 걸리는 비효율적인 작업이었습니다


SQL Server 2005부터 추가된 DMV에 있는 XML 플랜 정보를 바탕으로 인덱스 힌트를 사용한 SP 이름을 쉽게 확인 할 수 있습니다. 

아래 XML 플랜을 보시면 ForcedIndex항목으로 인덱스 힌트 사용 유무를 판단할 수 있습니다.
 <IndexScan Ordered="0" ForcedIndex="1" ForceSeek="0" NoExpandHint="0">
                    <DefinedValues>
                      <DefinedValue>
                        <ColumnReference Database="[TEST]" Schema="[dbo]" Table="[t1]" Column="c1" />

아래의 DMV 쿼리로 확인 할 수 있습니다.

SELECT

    db_name(st.dbid)

           ,object_name(st.objectid,st.dbid)

   ,st.text

   ,qp.query_plan

FROM sys.dm_exec_cached_plans cp

    cross apply sys.dm_exec_query_plan(cp.plan_handle) qp

    cross apply sys.dm_exec_sql_text(cp.plan_handle) as st

WHERE cp.cacheobjtype = 'Compiled Plan'

AND qp.query_plan.value('declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:IndexScan/@ForcedIndex)', 'float') =1

AND st.dbid IS NOT NULL


송 혁, SQL Server MVP
sqler.pe.kr // sqlleader.com
hyoksong.tistory.com