쿼리에 특정 계획을 사용하도록 SQL Server를 속이는 방법이 있는지 확인하려고합니다.
1. 환경
다른 프로세스간에 공유되는 데이터가 있다고 가정하십시오. 따라서 많은 공간을 차지하는 실험 결과가 있다고 가정합니다. 그런 다음 각 프로세스에 대해 사용하려는 실험 결과의 년 / 월을 알고 있습니다.
if object_id('dbo.SharedData') is not null
drop table SharedData
create table dbo.SharedData (
experiment_year int,
experiment_month int,
rn int,
calculated_number int,
primary key (experiment_year, experiment_month, rn)
)
go
이제 모든 프로세스에 대해 테이블에 매개 변수가 저장되었습니다.
if object_id('dbo.Params') is not null
drop table dbo.Params
create table dbo.Params (
session_id int,
experiment_year int,
experiment_month int,
primary key (session_id)
)
go
2. 시험 데이터
테스트 데이터를 추가하자 :
insert into dbo.Params (session_id, experiment_year, experiment_month)
select 1, 2014, 3 union all
select 2, 2014, 4
go
insert into dbo.SharedData (experiment_year, experiment_month, rn, calculated_number)
select
2014, 3, row_number() over(order by v1.name), abs(Checksum(newid())) % 10
from master.dbo.spt_values as v1
cross join master.dbo.spt_values as v2
go
insert into dbo.SharedData (experiment_year, experiment_month, rn, calculated_number)
select
2014, 4, row_number() over(order by v1.name), abs(Checksum(newid())) % 10
from master.dbo.spt_values as v1
cross join master.dbo.spt_values as v2
go
3. 페치 결과
이제 다음과 같은 방법으로 실험 결과를 얻는 것이 매우 쉽습니다 @experiment_year/@experiment_month
.
create or alter function dbo.f_GetSharedData(@experiment_year int, @experiment_month int)
returns table
as
return (
select
d.rn,
d.calculated_number
from dbo.SharedData as d
where
d.experiment_year = @experiment_year and
d.experiment_month = @experiment_month
)
go
계획은 훌륭하고 평행입니다.
select
calculated_number,
count(*)
from dbo.f_GetSharedData(2014, 4)
group by
calculated_number
쿼리 0 계획
4. 문제
그러나 데이터를 좀 더 일반적으로 사용하려면 다른 기능을 원합니다 dbo.f_GetSharedDataBySession(@session_id int)
. 그래서, 간단한 방법이 스칼라 함수, 번역을 생성하는 것입니다 @session_id
-> @experiment_year/@experiment_month
:
create or alter function dbo.fn_GetExperimentYear(@session_id int)
returns int
as
begin
return (
select
p.experiment_year
from dbo.Params as p
where
p.session_id = @session_id
)
end
go
create or alter function dbo.fn_GetExperimentMonth(@session_id int)
returns int
as
begin
return (
select
p.experiment_month
from dbo.Params as p
where
p.session_id = @session_id
)
end
go
이제 함수를 만들 수 있습니다 :
create or alter function dbo.f_GetSharedDataBySession1(@session_id int)
returns table
as
return (
select
d.rn,
d.calculated_number
from dbo.f_GetSharedData(
dbo.fn_GetExperimentYear(@session_id),
dbo.fn_GetExperimentMonth(@session_id)
) as d
)
go
쿼리 1 계획
데이터 액세스를 수행하는 스칼라 함수가 전체 계획을 serial로 만들기 때문에 계획은 물론 병렬이 아니라는 점을 제외하면 동일합니다 .
그래서 스칼라 함수 대신 하위 쿼리를 사용하는 것과 같은 여러 가지 접근법을 시도했습니다.
create or alter function dbo.f_GetSharedDataBySession2(@session_id int)
returns table
as
return (
select
d.rn,
d.calculated_number
from dbo.f_GetSharedData(
(select p.experiment_year from dbo.Params as p where p.session_id = @session_id),
(select p.experiment_month from dbo.Params as p where p.session_id = @session_id)
) as d
)
go
쿼리 2 계획
또는 사용 cross apply
create or alter function dbo.f_GetSharedDataBySession3(@session_id int)
returns table
as
return (
select
d.rn,
d.calculated_number
from dbo.Params as p
cross apply dbo.f_GetSharedData(
p.experiment_year,
p.experiment_month
) as d
where
p.session_id = @session_id
)
go
쿼리 3 계획
그러나이 쿼리를 스칼라 함수를 사용하는 쿼리만큼 훌륭하게 작성하는 방법을 찾을 수 없습니다.
몇 가지 생각 :
- 기본적으로 원하는 것은 SQL Server에 특정 값을 미리 계산 한 다음 상수로 더 전달하도록 지시하는 것입니다.
- 도움이 될 수있는 것은 중간 구체화 힌트 가있는 경우 입니다. 나는 몇 가지 변형 (멀티 스테이트먼트 TVF 또는 cte와 상단)을 확인했지만 지금까지 스칼라 함수를 사용하는 것만 큼 좋은 계획은 없습니다.
- SQL Server 2017- Froid : 관계형 데이터베이스의 명령형 프로그램 최적화 개선에 대해 알고 있지만 도움이 될지 확실하지 않습니다. 그래도 여기서 잘못 입증 된 것이 좋았을 것입니다.
추가 정보
일반적으로 @session_id
매개 변수 가있는 많은 다른 쿼리에서 사용하기가 훨씬 쉽기 때문에 테이블에서 직접 데이터를 선택하지 않고 함수를 사용 하고 있습니다.
실제 실행 시간을 비교하라는 요청을 받았습니다. 이 특별한 경우
- 쿼리 0은 ~ 500ms 동안 실행됩니다.
- 쿼리 1은 ~ 1500ms 동안 실행됩니다.
- 쿼리 1 ~ 1500ms 동안 실행
- 쿼리 3은 ~ 2000ms 동안 실행됩니다.
계획 # 2에는 탐색 대신 인덱스 스캔이 있으며 중첩 루프의 술어에 의해 필터링됩니다. 계획 # 3은 그렇게 나쁘지는 않지만 여전히 계획 # 0보다 더 많은 작업을 수행하고 느리게 작동합니다.
dbo.Params
거의 변경되지 않는다고 가정 하고 일반적으로 약 1-200 개의 행이 있으며 2000 개가 예상된다고 가정 해 봅시다. 현재 약 10 열이며 열을 너무 자주 추가하지는 않습니다.
Params의 행 수는 고정되어 있지 않으므로 모든 @session_id
행이 있습니다. 거기에 고정 된 열 수는 dbo.f_GetSharedData(@experiment_year int, @experiment_month int)
어디에서나 호출하지 않으려는 이유 중 하나 이므로이 쿼리에 내부적으로 새 열을 추가 할 수 있습니다. 제한이 있더라도 이에 대한 의견이나 제안을 듣고 기쁩니다.