카테고리 없음
플랜 변경으로 인한 cpu 100% 를 막아보자
보미아빠
2017. 11. 10. 10:23
oltp 서버에서는 traceflag 4136을 추천한다.
그러면, 최소한 가만히 있던 서버가 플랜이 바뀌어 cpu 를 100% 사용하는 일은 없어질것이다.
-- ------------------------------------
-- 데이터 생성 시작
-- ------------------------------------
use master
go
if db_id('plan_test') is not null
begin
alter database plan_test set single_user with rollback immediate
drop database plan_test
end
create database plan_test
go
use plan_test
go
if object_id('t_product') is not null
drop table t_product
go
-- 샵2개 상품 각 10만개 , 샵 100만개 넣어도 됨 하고 싶으면...
with temp as
(
select
top 200000 cast(row_number() over (order by (select 1)) as int) idx
, cast('contents other column' as char(400)) contentOtherCol
from sys.objects a1
cross join sys.objects a2
cross join sys.objects a3
cross join sys.objects a4
cross join sys.objects a5
)
select
idx
, cast(abs(checksum(newid())) % 2 as int) shopid
, cast(abs(checksum(newid())) % 100000 as int) productid
, contentOtherCol
into t_product
from temp
go
create clustered index cl_t_product on t_product (shopid, productid)
go
if object_id('t_img') is not null
drop table t_img
go
-- 샵과 상품으로 연결되는 이미지 1만개 type 은 모두 1, 다른거 100만개 넣어도 됨. 하고 싶으면 ...
with temp as
(
select
top 10000 cast(row_number() over (order by (select 1)) as int) idx
, cast('img other column' as char(400)) imgOtherCol
from sys.objects a1
cross join sys.objects a2
cross join sys.objects a3
cross join sys.objects a4
cross join sys.objects a5
)
select
idx
, cast(abs(checksum(newid())) % 2 as int) shopid
, cast(abs(checksum(newid())) % 100000 as int) productid
, 1 as imgType
, imgOtherCol
into t_img
from temp
go
create clustered index cl_t_img on t_img (imgType, shopid, productid)
go
-- ------------------------------------
-- 데이터 생성 끝
-- ------------------------------------
-- ------------------------------------
-- 생성된 데이터 보기 시작
-- ------------------------------------
dbcc show_statistics (t_product, cl_t_product)
dbcc show_statistics (t_img, cl_t_img)
select top 10 * from t_product
select top 10 * from t_img
-- ------------------------------------
-- 생성된 데이터 보기 끝
-- ------------------------------------
if object_id('a') is null
exec ('create proc a as select 1')
go
alter proc a
@shopid int
, @productid int
as
select top 1 *
from
(
select top 1 shopid, productid
from t_img
where
shopid = @shopid
and productid < @productid
and imgType = 1
order by productid desc
) a
left join t_product b
on a.shopid = b.shopid
and a.productid = b.productid
go
-- ------------------------------------
-- 테스트 스크립트 시작
-- ------------------------------------
set statistics io on
-- 정상적인 경우
exec a 1, 10000 -- 1번 shop 은 물건이 많고 상대적으로 많은 쿼리가 들어오고 있음 그러므로 첫 번째 컴파일 될 확율이 높음
exec a 3, 1 -- 이렇게 컴파일된 쿼리는 통계에 없는 변수가 들어와도 충분히 쓸만한 플랜임
-- 첫 변수를 통계에 없는 shopid 를 넣어 컴파일 한 경우, 성능에 심각한 오류가 생김
dbcc freeproccache
exec a 3, 1 -- 3번 샵은 신규 샵으로 상품이 적음 3번 샵과 1번으로 쿼리가 들어오면 아주 좋은 플랜임
exec a 1, 10000 -- 그러나 상품이 많은 1번 샵을 처리하기에는 부적합한 플랜임
-- 플랜 리뷰 및 설명
-- ------------------------------------
-- 테스트 스크립트 끝
-- ------------------------------------
-- ------------------------------------
-- work around 1 (뷰 안의 변수가 쿼리 외부로 나오지 못하게 강제로 막음)
-- ------------------------------------
go
alter proc a
@shopid int
, @productid int
as
select top 1 *
from
(
select top 1 shopid, productid % productid + productid as productid2 -- join 될 컬럼은 가공해 마지막 값만 = 비교가 일어날 수 있도록 바꾸어줌
from t_img
where
shopid = @shopid
and productid < @productid
and imgType = 1
order by productid desc -- sort 는 내부 컬럼을 그대로 이용해 인덱스를 이용한 소트를 사용 가능하게 만들어줌
) a
left join t_product b
on a.shopid = b.shopid
and a.productid2 = b.productid
go
-- ------------------------------------
-- work around 2 (optimize for unknown 으로 histogram 을 보지 못하게 쿼리 하도록 함)
-- ------------------------------------
go
alter proc a
@shopid int
, @productid int
as
select top 1 *
from
(
select top 1 shopid, productid
from t_img
where
shopid = @shopid
and productid < @productid
and imgType = 1
order by productid desc
) a
left outer join t_product b
on a.shopid = b.shopid
and a.productid = b.productid
option (OPTIMIZE FOR unknown)
go
-- ------------------------------------
-- work around 3 (querytraceon 4136을 이용해 ontimize for unknown 과 같은 효과를 query, instance 내 공통 적용 가능하게 만들 수 있음)
(database level 로 적용할 경우 2016부터... ALTER DATABASE SCOPED CONFIGURATION SET PARAMETER_SNIFFING = OFF;)
(요즘 추세가 azure 가 나오면서 database level 로 옵션을 변경할 수 있게 바뀌고 있음)
(https://www.mssqltips.com/sqlservertip/4245/sql-server-2016-database-scoped-configuration-options/)
-- ------------------------------------
alter proc a
@shopid int
, @productid int
as
select top 1 *
from
(
select top 1 shopid, productid
from t_img
where
shopid = @shopid
and productid < @productid
and imgType = 1
order by productid desc
) a
left outer join t_product b
on a.shopid = b.shopid
and a.productid = b.productid
option (querytraceon 4136)
go
-- ------------------------------------
-- 다른 시나리오
-- ------------------------------------
drop index t_product.cl_t_product
create nonclustered index nc_t_product_01 on t_product (shopid)
if object_id('c') is null
exec ('create proc c as select 1')
go
alter proc c
@shopid int
as
declare @c varchar(max) =N''
select @c = contentOtherCol
from t_product
where shopid = @shopid
select @c
go
alter proc c
@shopid int
as
declare @c varchar(max) =N''
select @c = contentOtherCol
from t_product
where shopid = @shopid
option (OPTIMIZE FOR unknown)
select @c
go
-- ------------------------------------
-- 테스트 스크립트 시작
-- ------------------------------------
-- 문제없는 컴파일 순서
exec c 1
exec c 3
-- 성능이 나빠지는 컴파일 순서
-- 그러나 변수 3에 대해서는 플랜이 좋음 이 모든건 histogram 을 보고 컴파일하는 parameter sniffing 문제임
exec c 3
exec c 1
-- ------------------------------------
-- 테스트 스크립트 끝
-- ------------------------------------
-- ------------------------------------
-- 다른 시나리오
-- ------------------------------------
create nonclustered index nc_t_product_01 on t_product (productid)
go
if object_id('b') is null
exec ('create proc b as select 1')
go
alter proc b
@productid int
as
declare @c varchar(max) =N''
select @c = contentOtherCol
from t_product
where productid < @productid
select @c
go
dbcc freeproccache
set statistics io on
set statistics time on
set statistics profile off
go
alter proc b
@productid int
as
declare @c varchar(max) =N''
select @c = contentOtherCol
from t_product
where productid < @productid
option (OPTIMIZE FOR unknown)
select @c
go
exec b 1
exec b 100000
go