2015-03-13 243 views
0

運行相對簡單的查詢時,我遇到了一些性能問題。這種情況如下:SQL Server 2008 R2:優化查詢性能

我有一個表,讓我們將其命名爲[Old_Table]所設置的是這樣的:

Document ID | Parsed_Codes 
-------------+------------- 
Document_1 | a 
Document_1 | b 
Document_1 | c 
Document_2 | a 
Document_2 | d 
Document_3 | a 
Document_3 | c 

該表共有250萬行,大約500K唯一[Document ID]值。

我想要做的就是總該表到一個新表命名爲[New_Table]它應該是這樣的:

Document ID | New_Parsed_Codes 
-------------+----------------- 
Document_1 | a; b; c 
Document_2 | a; d 
Document_3 | a; c 

爲了做到這一點我創建了以下查詢:

SELECT 
    t1.[Document ID] as [Document ID], 
    Stuff((SELECT '; ' + CONVERT(NVARCHAR(max), Parsed_Codes) 
      FROM dbo.[Old_Table] t2 
      WHERE t2.[Document ID] = t1.[Document ID] 
      FOR XML PATH('')), 1, 2, '') as [New_Parsed_Codes] 
INTO dbo.[New_Table] 
FROM dbo.[Old_Table] t1 
GROUP BY t1.[Document ID] 

現在的問題是這些數字看起來不太大,但查詢很容易花費16到32小時才能完成。我正在運行這臺機器有120GB RAM和24個核心。

現在的問題是;有什麼辦法可以改變查詢來提高效率。或者也許有不同的方法在一起

+0

表'字節'有多大? (例如,sp_spaceused的數據列的結果)。從這個例子我推斷出,我們正在談論的是250萬行,每個約40字節,給出的數據少於100兆(相關)。我假設你簡化了這個例子? – deroby 2015-03-13 20:39:25

回答

0

執行group_concat的另一種方法是使用cross apply而不是correlated subquery。嘗試這個。

SELECT t1.[Document ID] AS [Document ID], 
LEFT(cs.New_Parsed_Codes, Len(cs.New_Parsed_Codes) - 1) AS New_Parsed_Codes 
FROM Old_Table t1 
     CROSS APPLY (SELECT '; ' + CONVERT(NVARCHAR(max), Parsed_Codes) 
        FROM dbo.[Old_Table] t2 
        WHERE t2.[Document ID] = t1.[Document ID] 
        FOR XML PATH('')) cs ([New_Parsed_Codes]) 
GROUP BY t1.[Document ID], 
      LEFT(cs.New_Parsed_Codes, Len(cs.New_Parsed_Codes) - 1) 
+0

謝謝。我目前正在運行查詢,並會通知您是否改善了性能。 – 2015-03-13 15:02:14

0

由於涉及的人數(250萬行,看似很小的記錄)不健全過於龐大和描述的機器似乎令人印象深刻的,我不知道有多麼糟糕,這將在我的筆記本上運行。因此我創造了這個測試 '模擬' 的問題:

IF DB_ID('test') IS NULL CREATE DATABASE test 
GO 
USE test 
GO 

SET NOCOUNT ON 

PRINT Convert(varchar, current_timestamp, 113) + ' - starting up, creating t_old_table...' 

IF OBJECT_ID('t_old_table') IS NOT NULL DROP TABLE t_old_table 

GO 
CREATE TABLE t_old_table (row_id int IDENTITY(1, 1) PRIMARY KEY, 
          document_id nvarchar(50) NOT NULL, 
          parsed_codes nvarchar(50) NOT NULL) 
GO 

PRINT Convert(varchar, current_timestamp, 113) + ' - starting up, creating docuemnt_id''s...' 

-- create unique document_id's first 
DECLARE @counter int = 1, 
     @target int = 500000, 
     @block int = 10000 

INSERT t_old_table (document_id, parsed_codes) VALUES (Reverse(Convert(nvarchar(50), NewID())), Convert(nvarchar(50), BINARY_CHECKSUM(NewID()))) 

WHILE @counter < @target 
    BEGIN 
     INSERT t_old_table (document_id, parsed_codes) 
     SELECT TOP (CASE WHEN @counter + @block > @target THEN @target - @counter ELSE @block END) 
       Reverse(Convert(nvarchar(50), NewID())), 
       Convert(nvarchar(50), BINARY_CHECKSUM(NewID())) 
      FROM t_old_table 

     SELECT @counter = @counter + @@ROWCOUNT 
    END 

PRINT Convert(varchar, current_timestamp, 113) + ' - starting up, adding parsed codes...' 

-- add parsed-codes to existing document_id's 
SELECT @target = @target * 5 

WHILE @counter < @target 
    BEGIN 
     INSERT t_old_table (document_id, parsed_codes) 
     SELECT TOP (CASE WHEN @counter + @block > @target THEN @target - @counter ELSE @block END) 
       document_id, 
       Convert(nvarchar(50), BINARY_CHECKSUM(NewID())) 
      FROM t_old_table 
     ORDER BY NewID() -- some document_id's will have more, some will have less 

     SELECT @counter = @counter + @@ROWCOUNT 
    END 

UPDATE STATISTICS t_old_table 

PRINT Convert(varchar, current_timestamp, 113) + ' - Creating t_new_table...' 

GO 
IF OBJECT_ID('t_new_table') IS NOT NULL DROP TABLE t_new_table 
GO 
CREATE TABLE t_new_table (document_id nvarchar(50) NOT NULL PRIMARY KEY, 
          parsed_codes_list nvarchar(max) NOT NULL) 

GO 

運行這個花了大約6分鐘我(T)生鏽的酷睿i5筆記本

  • 2015年3月13日22:20:09:053 - 啓動,t_old_table ...
  • 2015年3月13日22:20:09:073 - 啓動,創建docuemnt_id的...
  • 2015年3月13日22:20:13:273 - ...
  • 2015年3月13日22:26:27:023 - 創建t_new_tabl è...

接下來,我採取了以下做法:

PRINT Convert(varchar, current_timestamp, 113) + ' - Creating #numbered table...' 

-- step 1, make temp-table that holds 'correct' order 
IF OBJECT_ID('tempdb..#numbered') IS NOT NULL DROP TABLE #numbered 
GO 
SELECT document_id, 
     parsed_codes, 
     order_nbr = ROW_NUMBER() OVER (PARTITION BY document_id ORDER BY parsed_codes) 
    INTO #numbered 
    FROM t_old_table 
WHERE 1 = 2 

CREATE UNIQUE CLUSTERED INDEX uq0 ON #numbered (order_nbr, document_id) 

INSERT #numbered 
SELECT document_id, 
     parsed_codes, 
     order_nbr = ROW_NUMBER() OVER (PARTITION BY document_id ORDER BY parsed_codes) 
    FROM t_old_table 

GO 
-- extract parsed codes 
DECLARE @nbr int = 1, 
     @rowcount int 

SET NOCOUNT OFF 
PRINT Convert(varchar, current_timestamp, 113) + ' - Converting to t_new_table, step ' + convert(varchar, @nbr) + '...' 

INSERT t_new_table (document_id, parsed_codes_list) 
SELECT document_id, parsed_codes 
    FROM #numbered 
WHERE order_nbr = @nbr 

SELECT @rowcount = @@ROWCOUNT 

UPDATE STATISTICS t_new_table 

WHILE @rowcount > 0 
    BEGIN 
     SELECT @nbr = @nbr + 1 

     PRINT Convert(varchar, current_timestamp, 113) + ' - Converting to t_new_table, step ' + convert(varchar, @nbr) + '...' 

     UPDATE t_new_table 
      SET parsed_codes_list = parsed_codes_list + ';' + n.parsed_codes 
      FROM t_new_table 
      JOIN #numbered n 
      ON n.document_id = t_new_table.document_id 
      AND n.order_nbr = @nbr 

     SELECT @rowcount = @@ROWCOUNT 
    END 

GO 

-- all done 
PRINT Convert(varchar, current_timestamp, 113) + ' - All done.' 

這確實需要相當長的一段整理的前期,但一旦事情變得循環中,連接實際上是非常簡單和快速。事實上,整個過程不到一分鐘。

  • 2015年3月13日22:26:27:030 - 創建#numbered表...
  • 2015年3月13日22:26:41:307 - 轉換到t_new_table,步驟1 ...
  • (500000行受影響)
  • 2015年3月13日22:26:45:543 - 轉換爲t_new_table,第2步...
  • (400986行(一個或多個)受影響)
  • 2015年3月13日22:26:49:863 - 轉換到t_new_table,步驟3 ...
  • (322042行(一個或多個)受影響)
  • [。 ..
  • 2015年3月13日22:27:15:713 - 轉換到t_new_table,步驟62 ...
  • (1行(一個或多個)受影響)
  • 2015年3月13日22:27:15:900 - 轉換爲t_new_table,步驟63 ...
  • (0 row(s)affected)
  • 2015年3月13日22:27:15:940 - 全部完成。

因爲我無法相信我的筆記本電腦/解決方案在於比你有什麼要快得多,我跑查詢

SELECT 
    t1.document_id as document_id, 
    Stuff((SELECT '; ' + CONVERT(NVARCHAR(max), parsed_codes) 
      FROM t_old_table t2 
      WHERE t2.document_id = t1.document_id 
      FOR XML PATH('')), 1, 2, '') as [New_parsed_codes] 
INTO dbo.[New_Table] 
FROM t_old_table t1 
GROUP BY t1.document_id 

,並在40秒內跑了。

這讓我相信你需要進一步解釋一下情況(大小確實很重要=),這樣我們才能更好地掌握時間花在哪裏;或者你有硬件問題...