对包含日期和nvarchar(50)的列使用聚簇索引而非非聚簇索引

时间:2020-06-07 09:21:22

标签: sql sql-server

我有一个名为“ 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
)

2 个答案:

答案 0 :(得分:1)

使用EXISTS代替COUNT有条件地插入行。由于不需要计数,因此效率更高。确保索引是唯一的,以确保不可能重复。

使用>=代替>作为时间戳记标准,以使2个具有相同时间戳记的会话都不会插入同一行,尽管如果存在唯一索引或约束,则会出错。 / p>

此外,考虑删除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

答案 1 :(得分:1)

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

第二,我非常同意Dan,如果您关心性能,应该使用EXISTS而不是SELECT COUNT(*)

第三,您正在从博客中获取错误消息。聚集索引的问题是数据按顺序存储在数据页上。如果具有聚集索引,则必须在其他行之间插入行时会遇到很大的性能瓶颈。

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

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

相关问题