【问题标题】:Using clustered than non-clustered index with columns that include date and nvarchar(50)对包含日期和 nvarchar(50) 的列使用聚集索引而不是非聚集索引
【发布时间】:2020-06-07 21:16:24
【问题描述】:

我有一个名为“GameTransactions”的表。表在性能方面运行良好至关重要(当站点将要运行时,表将有数百万条记录)。我想索引它。我用于列的列是:

UserID [int],
TransactionID [nvarchar(50)]
ProviderID [int]
TransactionTimeStamp [datetime]

关于我如何使用表格的一些背景信息。

在 SQL 操作开始时,我检查同一用户的事务 ID 是否存在。

   SELECT COUNT(1) 
    FROM GameTransactions WITH(NOLOCK)
    WHERE 
    UserID=@UserID AND
    TransactionID=@TransactionID 
    AND ProviderID=@ProviderID 
    AND TransactionTimeStamp>DATEADD(MONTH,-1,GETUTCDATE())

如果数据库中不存在该请求,我将其插入。

我选择使用以下索引

CREATE CLUSTERED INDEX IX_GameTransactions_UserID_TransactionID_ProviderID_TransactionTimeStamp
ON dbo.GameTransactions (UserID,TransactionID,ProviderID,TransactionTimeStamp);   

我在这篇文章中读到:

https://sqlstudies.com/2014/12/01/using-a-date-or-int-column-as-the-clustered-index/

将 datetime 作为聚集索引中的一列可以获得良好的性能。我不关心聚集索引要占用的磁盘空间,我更关心速度性能。

我也想过另一种解决方案,

 CREATE NONCLUSTERED INDEX IX_GameTransactions_UserID_TransactionID_ProviderID_TransactionTimeStamp
 ON dbo.GameTransactions (UserID, Month, Year,ProviderID)
 INCLUDE (TransactionID);

我可以添加 2 个额外的列 - 月和年。并使用整数而不是日期。请记住,“TransactionID”字段必须是 nvarchar(50)。没有办法解决它。

我有一个额外的 Id 列,它是自动递增的。这样的解决方案行得通吗?

  CONSTRAINT PK_GameTransactions PRIMARY KEY CLUSTERED (
      UserID
    , TransactionID
    , ProviderID
    , TransactionTimeStamp
, Id
)

【问题讨论】:

  • 所有这些非聚集索引会给我什么?除了更多的扫描时间和存储空间?!

标签: sql sql-server


【解决方案1】:

使用EXISTS 而不是COUNT 有条件地插入行。这将更有效,因为不需要计数。确保索引是唯一的,以确保不可能出现重复。

使用 >= 而不是 > 作为时间戳标准,这样具有相同时间戳的 2 个会话不会都插入同一行,尽管如果存在唯一索引或约束会出错。

此外,考虑删除 NOLOCK 以确保并发会话不会插入具有 TransactionTimeStamp 日期范围的相同 UserID/TransactionID/ProviderID 的行。为此,我建议SERIALIZABLE。下面的示例 DDL 将查询封装在下面的存储过程中,利用主键索引来实现性能和数据完整性。

CREATE TABLE dbo.GameTransactions(
      UserID int
    , TransactionID nvarchar(50)
    , ProviderID int
    , TransactionTimeStamp datetime
    CONSTRAINT PK_GameTransactions PRIMARY KEY CLUSTERED (
          UserID
        , TransactionID
        , ProviderID
        , TransactionTimeStamp
    )
);
GO

CREATE PROCEDURE dbo.InsertGameTransactions
      @UserID int
    , @TransactionID nvarchar(50)
    , @ProviderID int
AS
DECLARE @TransactionTimeStamp datetime = GETUTCDATE();
INSERT INTO dbo.GameTransactions (
      UserID
    , TransactionID 
    , ProviderID 
    , TransactionTimeStamp
)
SELECT
      @UserID
    , @TransactionID 
    , @ProviderID 
    , @TransactionTimeStamp
WHERE NOT EXISTS(
    SELECT 1
    FROM dbo.GameTransactions WITH(SERIALIZABLE)
    WHERE 
        UserID=@UserID AND
        TransactionID=@TransactionID 
        AND ProviderID=@ProviderID 
        AND TransactionTimeStamp >= DATEADD(MONTH,-1,@TransactionTimeStamp)
    );
GO

【讨论】:

  • 我可以看到您使用列作为主键。这似乎是一个很好的解决方案。该安全检查的唯一问题是它不能出现在我的存储过程的末尾(中间充满了其他 sql 语句)。其次,问题是我已经使用了一个自动递增的主键。我可以将它们全部组合在一起吗?只需将 Id 添加到集群键中。是否可以像这样配置它,ID 能够在每次插入后自动递增。
  • 我没有看到您的问题中引用的Id 列。它在查询中是如何使用的?有许多选项可以混合和匹配各种索引类型和键,但这取决于工作负载组合。我的回答只关注从您的问题中收集的条件插入查询。
  • 我有一个自动递增的 ID 列。我也需要该列作为主键。查看我的问题的更新。
  • 在这种情况下,自动增量Id 列不提供任何值作为索引键的一部分。如果没有在任何地方引用它为什么存在?
  • 它存在是因为我需要它作为外部实体的输出。我需要提供自己的交易 ID。
【解决方案2】:

首先,聚集索引对您的比较没有好处。

其次,我非常同意 Dan 的观点,如果您关心性能,您应该使用 EXISTS 而不是 SELECT COUNT(*)。

第三,您从博客中接收到 错误 消息。聚集索引的问题是数据按顺序存储在数据页上。当您拥有聚集索引时,当您必须在其他行“之间”插入行时,您可能会遇到很大的性能瓶颈。

因此,通常的建议是使用identity 列作为聚集索引键(顺便说一下,这是默认值)。这是个好建议,但也有其他情况。例如,newsequentialid() 是一个生成适用于聚集索引的 GUID 的函数,因为它们(几乎总是)在增加。

在您的情况下,索引中的第一列不是日期/时间。因此,在使用这样的聚集索引时,您可能会遇到大量碎片问题。对于您想要做的事情,没有理由对数据页上的数据进行排序。只需将常规索引与您需要的所有列用作键即可。

【讨论】:

  • '当您必须在其他行“之间”插入行时,您可能会遇到很大的性能瓶颈。' - 我永远不需要在其他行之间插入行。 '。只需将常规索引与您需要的所有列用作键。'-您是说我应该将列用作主键而不是使用聚集索引。为什么不为 where 子句中的几列创建集群或非集群来提高性能,为什么我的解决方案是错误的,我应该只使用一个自增主键?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-07
  • 2013-05-19
  • 2011-06-02
  • 1970-01-01
相关资源
最近更新 更多