Tôi đang cố gắng xem liệu có cách nào để lừa SQL Server sử dụng một kế hoạch nhất định cho truy vấn không.
1. Môi trường
Hãy tưởng tượng bạn có một số dữ liệu được chia sẻ giữa các quy trình khác nhau. Vì vậy, giả sử chúng ta có một số kết quả thử nghiệm cần nhiều không gian. Sau đó, với mỗi quy trình, chúng tôi biết năm / tháng của kết quả thử nghiệm mà chúng tôi muốn sử dụng.
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
Bây giờ, đối với mọi quy trình, chúng tôi có các tham số được lưu trong bảng
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. Kiểm tra dữ liệu
Hãy thêm một số dữ liệu thử nghiệm:
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. Lấy kết quả
Bây giờ, thật dễ dàng để có được kết quả thử nghiệm bằng cách @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
Kế hoạch là tốt đẹp và song song:
select
calculated_number,
count(*)
from dbo.f_GetSharedData(2014, 4)
group by
calculated_number
truy vấn 0 kế hoạch
4. Vấn đề
Nhưng, để làm cho việc sử dụng dữ liệu chung chung hơn một chút, tôi muốn có một chức năng khác - dbo.f_GetSharedDataBySession(@session_id int)
. Vì vậy, cách đơn giản sẽ là tạo các hàm vô hướng, dịch @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
Và bây giờ chúng ta có thể tạo chức năng của mình:
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
truy vấn 1 kế hoạch
Tất nhiên, kế hoạch là như nhau, ngoại trừ nó, không song song, bởi vì các hàm vô hướng thực hiện truy cập dữ liệu làm cho toàn bộ kế hoạch nối tiếp .
Vì vậy, tôi đã thử một vài cách tiếp cận khác nhau, như, sử dụng các truy vấn con thay vì các hàm vô hướng:
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
truy vấn 2 kế hoạch
Hoặc sử dụng 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
truy vấn 3 kế hoạch
Nhưng tôi không thể tìm ra cách viết truy vấn này tốt như truy vấn sử dụng các hàm vô hướng.
Vài suy nghĩ:
- Về cơ bản điều tôi muốn là có thể bằng cách nào đó nói với SQL Server để tính toán trước các giá trị nhất định và sau đó chuyển chúng đi xa hơn như các hằng số.
- Điều có thể hữu ích là nếu chúng ta có một số gợi ý vật chất hóa trung gian . Tôi đã kiểm tra một vài biến thể (TVF đa câu hoặc cte có đầu), nhưng không có kế hoạch nào tốt bằng một biến thể có chức năng vô hướng cho đến nay
- Tôi biết về sự cải tiến sắp tới của SQL Server 2017 - Froid: Tối ưu hóa các chương trình bắt buộc trong cơ sở dữ liệu quan hệ . Tuy nhiên, tôi không chắc chắn nó sẽ giúp ích. Dù vậy, thật tốt khi được chứng minh là sai ở đây.
Thông tin thêm
Tôi đang sử dụng một hàm (thay vì chọn dữ liệu trực tiếp từ các bảng) bởi vì nó dễ sử dụng hơn trong nhiều truy vấn khác nhau, thường có @session_id
dưới dạng tham số.
Tôi được yêu cầu so sánh thời gian thực hiện thực tế. Trong trường hợp cụ thể này
- truy vấn 0 chạy trong ~ 500ms
- truy vấn 1 chạy trong ~ 1500ms
- truy vấn 2 chạy trong ~ 1500ms
- truy vấn 3 chạy trong ~ 2000ms.
Kế hoạch số 2 có quét chỉ mục thay vì tìm kiếm, sau đó được lọc bởi các vị từ trên các vòng lặp lồng nhau. Kế hoạch số 3 không tệ lắm, nhưng vẫn làm được nhiều việc hơn và hoạt động chậm hơn kế hoạch số 0.
Giả sử rằng dbo.Params
hiếm khi được thay đổi và thường có khoảng 1-200 hàng, không quá, giả sử 2000 được mong đợi. Bây giờ là khoảng 10 cột và tôi không mong đợi thêm cột quá thường xuyên.
Số lượng hàng trong Params không cố định, vì vậy cứ mỗi hàng @session_id
sẽ có một hàng. Số lượng cột không cố định, đó là một trong những lý do tôi không muốn gọi dbo.f_GetSharedData(@experiment_year int, @experiment_month int)
từ mọi nơi, vì vậy tôi có thể thêm cột mới vào truy vấn này trong nội bộ. Tôi rất vui khi nghe bất kỳ ý kiến / đề xuất nào về vấn đề này, ngay cả khi nó có một số hạn chế.