2013-07-03 67 views
0

我有2個表(不能更改)SQL Server的親子加入和緩慢的查詢性能

Parent (id, date, amount) 
Child (parent_id, key, value) 

指標

Parent.pk (id) 
Parent.idx1 (id, date) include (amount) 
Child.pk (parent_id, key) 
Child.idx1 (parent_id, key, value) 

和查詢

select sum(amount) 
from Parent as p 
left outer join Child as c1 on c1.parent_id = p.id and c1.key = 'X' 
left outer join Child as c2 on c2.parent_id = p.id and c2.key = 'Y' 
where p.date between '20120101' and '20120131' 
and c1.value = 'x1' 
and c2.value = 'y1' 

問題是性能。
家長有〜1個500 000記錄和子〜6 000 000條記錄

以1

這個查詢大約需要3秒是太多了我的情況 - 它必須比幾毫秒的時間少

執行計劃顯示我的SQL Server正在做Parent.idx1索引掃描,比合並與Child.idx1聚集索引加入尋求 - 因爲它,當我按日期過濾它們掃描整個150萬點的記錄,即使這不是最佳的。

取2

當我改變Parent.idx1

Parent.idx1 (date, id) include (amount) 

的SQL Server選擇聚集索引掃描Parent.pk比再次合併與Child.idx1加入。執行時間約爲6秒。

取3

當我強制使用Parent.idx1 (date, id) include (amount)然後排序結果前合併連接和執行時間還差〜11S。

以4

試圖創建索引視圖,但JOIN不能使用它,因爲左外。

有沒有什麼辦法可以做出這樣的查詢 - Parent-Child與它們兩個上的過濾器連接 - 更快?
沒有反規範化。

更新2013年7月4日:
要回答那些使用INNER JOIN - 是它的速度更快,但我不能使用它。
我在這裏展示的是我真正需要的簡化版本。
我需要爲MS動態導航「總帳條目」(父)和「總帳條目維度」(子)表創建SQL視圖,以便我能夠從該應用程序讀取它。 完整視圖貌似現在這種權利:

create view analysis 
as 
select 
    v.id as view_id 
    , p.date 
    , p.Amount 
    , c1.value as value1 
    , c2.value as value2 
    , c3.value as value3 
    , c4.value as value4 
from Parent as p 
    cross join analysis_view as v 
    left outer join Child as c1 on c1.parent_id = p.id and c1.key = v.key1 
    left outer join Child as c2 on c2.parent_id = p.id and c2.key = v.key2 
    left outer join Child as c3 on c3.parent_id = p.id and c3.key = v.key3 
    left outer join Child as c4 on c4.parent_id = p.id and c4.key = v.key4 

其中analysis_view包含8條記錄當前,看起來像這樣:analysis_view (id, key1, key2, key3, key4)
然後aplication可以查詢它像這樣

select sum(amount) 
from analysis 
where view_id = 1 and date between '20120101' and '20120131' 
and value1 = 'x1' 
and value2 = 'x2' 

select sum(amount) 
from analysis 
where view_id = 1 and date between '20120101' and '20120131' 
and value1 = 'x1' 
and value3 = 'z1' 

MS動態資產淨值已對其進行了非標準化表格以及來自其的查詢速度很快,但在我們的情況下(〜10GB)非常巨大,並且在somone創建新的分析視圖時將整個系統鎖定約一個小時。 另外NAV不知道如何產生連接,這就是爲什麼我必須在SQL Server端定義它。

+0

「X」和「Y」是「key」的唯一值嗎? –

+0

如果它有一個X鍵和一個Y鍵的孩子,你是否應該計算兩次記錄的數量?或者如果它有一個X或Y的孩子,你是否應該使用該數額進行求和?因爲你在做第一個,但第二個更常見。 –

+1

「少於幾毫秒」意味着所有需要的數據都將處於高速緩存中,或者任何I/O都將完成到SSD,可能位於RAID集中以提高讀取帶寬。 – HABO

回答

0

我正在運行幾次嘗試,但我沒有發現任何運行速度比修復索引快得多的東西。

以1: 創建一個處理父對象和第一個子對象(不能有兩個對實例化視圖中同一個表的引用)的物化視圖,然後將它連接到查詢中的子對象 - 速度並不快。

以2: 使用父和子2創建第二個物化視圖,並在兩個物化視圖之間再次使用連接 - 速度並不快。

以3: 使用INTERSECT而不是JOIN將兩個物化視圖合併在一起 - 速度並不快。

以4: 在物化視圖進入的年份和月份列打出來的日期時間 - 快不了多少(實際上更慢)

最大的問題似乎是你對孩子表約束兩次,這消除了以任何有效方式進行物化視圖的能力。我可以編寫一個物化視圖,通過預聚合它們,可以使每月總計尋找具有'X'和'X1'子鍵的父母,但是沒有足夠的信息返回到過濾器取出沒有child2關係的金額。

這和我是懶惰,試着用1/10你的數據量進行性能測試,我的成績依然非常快(200毫秒<)無論我做了什麼。我現在正在構建一套完整的測試數據,但顯然我不知道你的分佈是什麼。這有助於瞭解1,500,000條記錄中有多少記錄有X個孩子,Y個孩子和兩個X & Y孩子。如果這是一個固定的查詢或者鍵/值將在運行時改變。

這裏是我的測試腳本: 設置:

CREATE TABLE Parent (id int NOT NULL CONSTRAINT parent_pk PRIMARY KEY, date datetime, amount decimal(10,2) NOT NULL) 
CREATE TABLE Child (parent_id int NOT NULL, [key] char(1) NOT NULL, value char(2) NOT NULL, CONSTRAINT child_pk PRIMARY KEY (parent_id,[key])) 
CREATE INDEX Parent_IDX ON Parent (id,date,amount) 
CREATE INDEX Child_IDX ON Child (parent_id,[key],value) 

DECLARE @RowCount INT 
DECLARE @Random INT 
DECLARE @Upper INT 
DECLARE @Lower INT 
DECLARE @InsertDate DATETIME 
DECLARE @keys INT 
DECLARE @key INT 

SET @Lower = 0 
SET @Upper = 500 
SET @RowCount = 0 
WHILE @RowCount < 15000 
BEGIN 

SELECT @Random = ROUND(((@Upper - @Lower -1) * RAND() + @Lower), 0) 
SET @InsertDate = DATEADD(dd, @Random, GETDATE()) 

INSERT INTO Parent(id,date,amount) 
VALUES (@RowCount , @InsertDate ,@Random) 

SET @keys=ROUND(RAND()*3+1,0) 
SET @key=0 
WHILE @key<@keys 
BEGIN 
INSERT INTO Child(parent_id,[key],value) 
VALUES (@RowCount,SUBSTRING('XYZ',@key+1,1),SUBSTRING('XYZ',@key+1,1)+'1') 
SET @[email protected]+1 
END 

SET @RowCount = @RowCount + 1 
END 

我的便箋:

SELECT COUNT(*) ParentCount FROM Parent 
GO 
SELECT COUNT(*) ChildCount FROM Child 
GO 
CREATE INDEX Parent_IDX2 ON Parent(date,id) 
GO 
CREATE VIEW blah WITH SCHEMABINDING AS 
SELECT p.id,p.amount,DATEPART(YEAR,p.date) AS yy,DATEPART(Month,p.date) AS mm 
from dbo.Parent as p 
join dbo.Child as c1 on c1.parent_id = p.id and c1.[key] = 'X' and c1.value = 'x1' 
--join dbo.Child as c2 on c2.parent_id = p.id and c2.[key] = 'Y' and c2.value = 'y1' 
GO 
CREATE UNIQUE CLUSTERED INDEX blah_pk ON blah (id) 
CREATE INDEX blah_IDX ON blah (yy,mm,amount) 
GO 
CREATE VIEW blah2 WITH SCHEMABINDING AS 
SELECT p.id,p.amount,DATEPART(YEAR,p.date) AS yy,DATEPART(Month,p.date) AS mm 
from dbo.Parent as p 
join dbo.Child as c1 on c1.parent_id = p.id and c1.[key] = 'Y' and c1.value = 'y1' 

GO 
CREATE UNIQUE CLUSTERED INDEX blah2_pk ON blah2 (id) 
CREATE INDEX blah2_IDX ON blah2 (yy,mm,amount) 
GO 
select sum(amount) 
from Parent as p 
join Child as c1 on c1.parent_id = p.id and c1.[key] = 'X' and c1.value = 'x1' 
join Child as c2 on c2.parent_id = p.id and c2.[key] = 'Y' and c2.value = 'y1' 
where p.date between '20130801' and '20130831' 
GO 
select sum(amount) 
from blah p 
join Child as c2 on c2.parent_id = p.id and c2.[key] = 'Y' and c2.value = 'y1' 
where p.yy=2013 and p.mm=8 
GO 
SELECT sum(blah.amount) 
FROM blah 
JOIN blah2 ON blah.id=blah2.id AND blah.yy=blah2.yy AND blah.mm=blah2.yy and blah.amount=blah2.amount 
where blah.yy=2013 and blah.mm=8 

SELECT SUM(amount) 
FROM (
SELECT * 
FROM blah 
where blah.yy=2013 and blah.mm=8 
INTERSECT 
SELECT * 
FROM blah2 
where blah2.yy=2013 and blah2.mm=8 
) t1 
0

但是也有一些影響性能(雖然我不是專家)幾件事情。其中之一就是在Child上有一個索引,該索引的每一列都作爲索引的主要列,這並沒有什麼意義。另一件事是,您要根據表c1c2的值過濾您的查詢,將您的查詢轉換爲INNER JOIN。你應該嘗試修改它使用EXISTS代替,像這樣:

select sum(amount) 
from Parent as p 
where p.date between '20120101' and '20120131' 
and exists(select 1 from Child 
      where parent_id = p.id and key = 'X' 
      and value = 'x1') 
and exists(select 1 from Child 
      where parent_id = p.id and key = 'Y' 
      and value = 'y1') 
+1

OP的查詢的語義要求找到_both_'x1'和'y1'值。你的查詢將會匹配,但不需要配對。 – HABO

+0

@HABO你是對的,謝謝 – Lamak

1

更改您的LEFT JOIN到INNER JOIN。謂詞c1.value ='x1'無論如何要放棄左外行。

+0

看到更新,我解釋了爲什麼我不能使用它 – SeeR