2015-03-13 200 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秒内跑了。

这让我相信你需要进一步解释一下情况(大小确实很重要=),这样我们才能更好地掌握时间花在哪里;或者你有硬件问题...