微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何避免SQL Server对缺失值使用默认行估计?

如何解决如何避免SQL Server对缺失值使用默认行估计?

我得到了一个包含两百万行的表,其中有一个Status列,这是非聚集索引中的第二列。 状态为char(10),包含“新建”,“正在处理”,“已处理”和“失败”

轮询功能会检查新行

SELECT TOP 1 ... FROM Table WHERE firstColumnInIdex = 1 AND Status = 'New' ORDER BY Id

(这实际上是对“处理中”状态的更新以及其他一些区别,但这并不重要)

查询使用非聚集索引,但行估计约为行的30%,因此内存授权在GB范围内。

我的测试表明问题出在统计上。由于表中通常不存在状态为“新建”的行,因此统计信息中不会显示“新建”(表示“已处理”为数百万,而“失败”则为数万)。如果在统计信息中找不到该值,则sql Server似乎是认估计值,在这种情况下,大约为行的30%。

我在状态为“ New”的表中添加了一行,并使用FULLSCAN norECOmpuTE创建了新的统计信息。 (因此它变成了数百万个“已处理”,数千个“失败”和1个“新”)

现在行估计为1行,并且在获得少量内存的情况下查询成本从82下降到6。

(丢弃统计信息将再次导致30%的损失)

尽管此技巧可以解决问题,但感觉好像是黑客,有一天可能会停止工作(例如,将来的dba会找到此过时的统计信息并将其删除/更新)。

有没有更好的方法解决这个问题?例如

  • 改为使用整数状态?
  • 通过外键或约束使sql Server知道“新”状态吗?

版本为2016SP1

解决方法

我觉得有用的一件事是filtered index

假设这是一个队列,并且一切以状态“ new”开始,您

  • 选择一个或所有“新”行(获取PK ID)
  • 根据这些ID进行操作
  • 根据ID更新状态

在这种情况下,您可以创建一个过滤索引,该索引基本上只是状态为'new'的所有行的最新列表。

CREATE NONCLUSTERED INDEX ix_myindex ON [myTable] 
([ID])
WHERE (Status = 'New')

请注意-索引将非常“热”,例如,有很多更改(一旦不再是“新”,它们就会从索引中删除)。

但是,我们的想法是保持很小,以至于没关系。

确保索引具有标识相关行(例如PK)所需的所有字段,以使其尽可能简单/小巧,并查看其是否有效。

更新以下评论

这些问题可能与“升序关键问题”有关-请随时进行研究和审查。

我在上面可能犯了一个小错误-如果实际上包括要过滤的字段,通常过滤后的索引会更好地工作。因此,以下方法可能会更好。

CREATE NONCLUSTERED INDEX ix_myindex ON [myTable] 
([ID],[Status])
WHERE (Status = 'New')

关于解决方案中的方法-想法是我们将完全忽略统计信息。相反,我们实际上创建了一个具有相关行数的临时表,这些表将限制基数估计。

为了进行测试,我有一个名为“ test”的表,该表具有约150万行,一个ID PK和4个带有UUID(本质上是随机数据)的列。

我用它来创建一个带有状态列的新表“ test2”。其中大约80%的状态为“已处理”,10%的状态为“正在处理”,10%的状态为“失败”。

然后我插入状态为“新”的新行。请注意,统计信息不更新

但是,我然后使用过滤后的索引来识别相关行,方法是将它们放入临时表中-并将该表用于进一步处理。

设置

IF OBJECT_ID (N'test2',N'U') IS NOT NULL DROP TABLE dbo.Test2;
GO

CREATE TABLE [dbo].[test2](
    [ID] [int] NOT NULL,[Status] [varchar](12) NULL,[col2] [varchar](100) NULL,[col3] [varchar](100) NULL,[col4] [varchar](100) NULL,[col5] [varchar](100) NULL,CONSTRAINT [PK_test2] PRIMARY KEY CLUSTERED ([ID] ASC)
 );
GO

CREATE NONCLUSTERED INDEX [IX_test2_StatusNew] ON [dbo].[test2] ([ID] ASC,[Status] ASC)
    WHERE ([Status]='New');
GO

INSERT INTO dbo.Test2 (ID,Status,Col2,Col3,Col4,Col5)
    SELECT ID,CASE WHEN ID % 12 < 10 THEN 'Processed' WHEN ID % 12 = 10 THEN 'Processing' ELSE 'Failed' END,Col5
    FROM dbo.Test;
GO

CREATE STATISTICS [S_Status] ON [dbo].[test2]([Status]);
GO

DBCC SHOW_STATISTICS ('dbo.Test2','S_Status');
/*
RANGE_HI_KEY  RANGE_ROWS EQ_ROWS   DISTINCT_RANGE_ROWS  AVG_RANGE_ROWS
Failed        0          141420    0                    1
Processed     0          1417080   0                    1
Processing    0          141420    0                    1
*/

这是我的存储过程-首先标记适当的行(将其状态更改为“正在处理”)并记录其ID。

然后将这些ID用于处理表中的行,然后将状态再次更新为“已处理”。

为简便起见,我没有进行任何交易或错误检查。

CREATE PROCEDURE UpdateTest2News
AS
BEGIN
SET NOCOUNT ON;

    CREATE TABLE #IDs_to_process (ID int PRIMARY KEY);

    UPDATE      test2
        SET     Status = 'Processing'
        OUTPUT  deleted.ID
        INTO    #IDs_to_process
        WHERE   Status = 'New';

    UPDATE      test2
        SET     Col2 = NEWID(),Col3 = NEWID(),Col4 = NEWID(),Col5 = NEWID()
        FROM    test2
                INNER JOIN #IDs_to_Process IDs ON test2.ID = IDs.ID;

    UPDATE      test2
        SET     Status = 'Processed'
        FROM    test2
                INNER JOIN #IDs_to_Process IDs ON test2.ID = IDs.ID;

END;

然后我向Test2添加一个新行(状态为“新建”)。在查看统计信息时,它们没有变化(发生的变化不足以强制更新)。

SELECT TOP 1 ID FROM dbo.test2 ORDER BY ID DESC; -- Getting the latest value for next step
/* Max ID = 1699920 */

INSERT INTO dbo.Test2 (ID,Col5)
SELECT 1699921,'New',NULL,NULL;

DBCC SHOW_STATISTICS ('dbo.Test2','S_Status');
/*  Same as above  */
DBCC SHOW_STATISTICS ('dbo.Test2','IX_test2_StatusNew');
/*  No records represented in stats  */
GO

现在,最后一步

  • 运行SET STATISTICS TIME,IO ON;以查看处理状态
  • 还设置“包括实际执行计划”以查看估算值与实际值等
EXEC UpdateTest2News

以下是清理后的版本统计信息-非常不错。

Stats summary

SQL Server parse and compile time: 
   CPU time = 0 ms,elapsed time = 1 ms.

Table '#IDs_to_process___...________________0000000000BC'. Scan count 0,logical reads 2
Table 'test2'. Scan count 1,logical reads 7
Table 'Worktable'. Scan count 1,logical reads 5

 SQL Server Execution Times:
   CPU time = 0 ms,elapsed time = 14 ms.


SQL Server parse and compile time: 
   CPU time = 25 ms,elapsed time = 25 ms.

Table 'test2'. Scan count 0,logical reads 11
Table '#IDs_to_process________...__________0000000000BC'. Scan count 1,logical reads 2

 SQL Server Execution Times:
   CPU time = 0 ms,elapsed time = 593 ms.


SQL Server parse and compile time: 
   CPU time = 0 ms,elapsed time = 1 ms.

Table 'test2'. Scan count 0,logical reads 3
Table '#IDs_to_process_____...______0000000000BC'. Scan count 1,elapsed time = 45 ms.


 SQL Server Execution Times:
   CPU time = 61 ms,elapsed time = 683 ms.

这是执行计划/等,估计值与实际值也很好。

enter image description here


注意-它确实记住/缓存执行计划,当您拥有数量迥异的“新”行时,这可能会成为问题。

如果需要,可以将OPTION (RECOMPILE)放在存储过程中的语句2或3上,以便对行数进行新的估算。

如果需要的话,命令UPDATE STATISTICS test2 (IX_test2_StatusNew) WITH fullscan的运行也很简单(因为该索引中几乎没有行)-可能对您有帮助。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。