为每个父级选择前1条记录时,子查询太慢

时间:2012-08-24 20:41:08

标签: sql-server performance sql-server-2008-r2 database-performance

我试图使用子查询方法找到所选日期内的最后一条记录。问题是查询太慢了。我想知道是否有人对如何重写此查询以提高性能有任何想法。我的服务器正因此而死。
为了便于测试,我创建了一个表变量来生成用于测试目的的虚假数据。要测试此脚本,请运行usp_ExtractData'400000'

我关注的是--- B部分 对于400000 * 3 = 1200000条记录,我的结果是18秒。在真正的数据库上,我做索引并每晚重新索引。

--Store proceedure with table variable data

ALTER PROCEDURE [dbo].[usp_ExtractData](
@TotalRecord int--Create random records for each product
)

AS
BEGIN
    --MS SQL 2008
    SET NOCOUNT ON;

    --SECTION 1--Create test data--- GO TO SECTION 2
        --Create Variable table to Products fake data
        DECLARE @Product TABLE
        (
          ProductID int primary  key not null
          ,SKU varchar(100) not null
        )
        --Insert couple records into @Product table
        INSERT INTO @Product(ProductID, SKU) VALUES     (100,'CUP100')
        INSERT INTO @Product(ProductID, SKU) VALUES     (101,'CUP101')
        INSERT INTO @Product(ProductID, SKU) VALUES     (102,'MUG101')

        --Create Variable table to hold Products History data
        DECLARE @History TABLE
        (
           ID int identity not null
          ,ProductID int not null
          ,VisitedDatetime datetime not null
        )

        --Generate random record for testing
        WHILE @TotalRecord>0
            BEGIN
                INSERT INTO  @History( ProductID, VisitedDatetime) VALUES (100,DATEADD(minute,rand()*100,GETDATE()))
                INSERT INTO  @History( ProductID, VisitedDatetime) VALUES (101,DATEADD(minute,rand()*100,GETDATE()))
                INSERT INTO  @History( ProductID, VisitedDatetime) VALUES (102,DATEADD(minute,rand()*100,GETDATE()))
                set @TotalRecord=@TotalRecord-1
            END
    --SECTION 1--Finised creating test data



    ---SECTION B 

      --SELECTION B1- SEE DATA
      SELECT * FROM @History         ORDER BY ProductID, VisitedDatetime DESC
        --Run query to find the last visit per each ProductID

        --THIS IS TOO SLOW
        DECLARE @TestPerformanceDatetime datetime--Test performance
        SET @TestPerformanceDatetime= GETDATE()
        SELECT  *, (select top(1) VisitedDatetime FROM @History as t2 WHERE t2.ProductID=ProductID and VisitedDatetime BETWEEN GETDATE() AND GETDATE()+10 ORDER BY VisitedDatetime DESC) as LastVistiDate
        FROM     @Product

        --Display the performance
        SELECT  DATEDIFF(SECOND, @TestPerformanceDatetime,getdate()) AS TotalSeconds
    ---SECTION B - End
END

3 个答案:

答案 0 :(得分:2)

使用cross applymax()

select *
from @Product p
cross apply (
    select MAX(VisitedDatetime) LastVisitedDatetime
    from @History
    where VisitedDatetime BETWEEN GETDATE() AND GETDATE()+10
        and ProductID = p.ProductID
) h

答案 1 :(得分:0)

我使用此查询的原始版本获得0秒,因此我将随机测试记录的数量从400,000增加到4,000,000。

CREATE PROCEDURE [dbo].[usp_ExtractData_test](
@TotalRecord int--Create random records for each product
)

AS
BEGIN
    --MS SQL 2008
    SET NOCOUNT ON;

    --SECTION 1--Create test data--- GO TO SECTION 2
        --Create Variable table to Products fake data
        DECLARE @Product TABLE
        (
          ProductID int primary  key not null
          ,SKU varchar(100) not null
        )
        --Insert couple records into @Product table
        INSERT INTO @Product(ProductID, SKU) VALUES     (100,'CUP100')
        INSERT INTO @Product(ProductID, SKU) VALUES     (101,'CUP101')
        INSERT INTO @Product(ProductID, SKU) VALUES     (102,'MUG101')

        --Create Variable table to hold Products History data
        DECLARE @History TABLE
        (
           ID int identity not null
          ,ProductID int not null
          ,VisitedDatetime datetime not null
        )

        --Generate random record for testing
        WHILE @TotalRecord>0
            BEGIN
                INSERT INTO  @History( ProductID, VisitedDatetime) VALUES (100,DATEADD(minute,rand()*100,GETDATE()))
                INSERT INTO  @History( ProductID, VisitedDatetime) VALUES (101,DATEADD(minute,rand()*100,GETDATE()))
                INSERT INTO  @History( ProductID, VisitedDatetime) VALUES (102,DATEADD(minute,rand()*100,GETDATE()))
                set @TotalRecord=@TotalRecord-1
            END
    --SECTION 1--Finised creating test data



    ---SECTION B 
        --Run query to find the last visit per each ProductID
        --THIS IS TOO SLOW
        DECLARE @TestPerformanceDatetime datetime--Test performance
        SET @TestPerformanceDatetime= GETDATE()
        SELECT  P.*, LastVisitDate.VisitedDatetime
        FROM     @Product P
        LEFT
        JOIN  (select top(1) T2.VisitedDatetime FROM @History as t2
               ORDER BY T2.VisitedDatetime DESC) as LastVisitDate
          ON  LastVisitDate.VisitedDatetime BETWEEN GETDATE() AND GETDATE()+10

        --Display the performance
        SELECT  DATEDIFF(SECOND, @TestPerformanceDatetime,getdate()) AS TotalSeconds
    ---SECTION B - End
END

Proof

答案 2 :(得分:0)

在我的笔记本上,我看到生成历史记录的时间约为102,346毫秒,第一次搜索时为5,120毫秒,第二次搜索时为643毫秒。 OTOH,BOINCing Rosetta @ Home同时出局。

declare @HistoryRecordsPerProduct int = 400000

set nocount on

-- drop table #Product
-- drop table #History

-- Create the test tables.

create table #Product
  ( ProductId Int primary key not null, SKU VarChar(100) not null )

insert into #Product ( ProductId, SKU ) values
  ( 100, 'CUP100' ), ( 101, 'CUP101' ), ( 102, 'MUG102' )

create table #History 
  ( Id Int identity not null, ProductId Int not null, VisitedDatetime DateTime not null )
-- EDIT: Note the following index on both columns. 
create index History_Product_VisitedDateTime on #History ( ProductId, VisitedDateTime desc )

-- Populate the history table.
declare @Start as DateTime = GetDate()

while @HistoryRecordsPerProduct > 0
  begin
  insert into #History ( ProductId, VisitedDatetime ) values ( 100, DateAdd( minute, rand() * 100, GetDate() ) ) 
  insert into #History ( ProductId, VisitedDatetime ) values ( 101, DateAdd( minute, rand() * 100, GetDate() ) ) 
  insert into #History ( ProductId, VisitedDatetime ) values ( 102, DateAdd( minute, rand() * 100, GetDate() ) ) 
  set @HistoryRecordsPerProduct = @HistoryRecordsPerProduct - 1 
  end 

select DateDiff( ms, @Start, GetDate() ) as 'Elapsed History Generation (ms)'

-- Query the data.
set @Start = GetDate()
declare @End as DateTime = @Start + 10 -- Days.

select @Start as [Start], @End as [End]

select ProductId, SKU,
  ( select Max( VisitedDateTime ) from #History where ProductId = #Product.ProductId and
    @Start <= VisitedDatetime and VisitedDatetime <= @End ) as VDT
  from #Product

select DateDiff( ms, @Start, GetDate() ) as 'Elapsed Search (ms)'

-- And again with the data cached.

set @Start = GetDate()
set @End = @Start + 10 -- Days.

select @Start as [Start], @End as [End]

select ProductId, SKU,
  ( select Max( VisitedDateTime ) from #History where ProductId = #Product.ProductId and
    @Start <= VisitedDatetime and VisitedDatetime <= @End ) as VDT
  from #Product

select DateDiff( ms, @Start, GetDate() ) as 'Elapsed Search (ms)'