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

数百万行表优化SQL Server

如何解决数百万行表优化SQL Server

我有一个包含数百万行的表:

CREATE TABLE [dbo].[RequestIdentities]
(
    [Id] [bigint] IDENTITY(1,1) NOT NULL,[UniqueKey] [nvarchar](256) NULL,[Timestamp] [datetime] NULL,CONSTRAINT [PK_RequestIdentities] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
                    WITH (PAD_INDEX = OFF,STATISTICS_norECOmpuTE = OFF,IGnorE_DUP_KEY = OFF,ALLOW_ROW_LOCKS = ON,ALLOW_PAGE_LOCKS = ON,FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[RequestIdentities] 
    ADD CONSTRAINT [DF_RequestIdentities_Timestamp]  
        DEFAULT (GETDATE()) FOR [Timestamp]
GO 

Web API使用ADO.NET操纵数据库并执行以下操作:

  1. 执行此查询

     SELECT 1 
     FROM RequestIdentities WITH (nolock) 
     WHERE UniqueKey = @key
    
  2. 如果存在:

     if(reader.HasRows)
    
  3. 返回http响应。

  4. 否则,它将id插入到表中:

     INSERT INTO RequestIdentities(UniqueKey) 
     VALUES(@key)
    

每分钟有数百次插入/更新,我可以做一些优化表的事情,例如自定义统计信息/索引吗?

解决方法

对于使用SSD的计算机上的现代数据库,

1M行的数据不是很多。几百个插入也不多。虽然可以使用内存表进行优化,但必须先消除现有问题。

在某些情况下,内存表也可以简化维护。

问题

此代码包含一些会损害性能的问题。

  • WITH (NOLOCK)是一个非常糟糕的主意,它根本不会提高性能。实际上,它需要*更广泛的锁(模式级别),读取脏的,未提交的数据,可以返回两次相同的数据,甚至可能引发错误。
  • 该代码执行两次远程调用,导致插入一行的延迟增加了一倍。除了延迟之外,这意味着在SELECT操作期间获取的锁需要保留的时间远远超过所需的时间,这可能会阻止其他尝试使用同一表的连接。
  • TOP 1最好是无人值守。如果UniqueKey确实是唯一的,则只会返回一个结果。

修复

您可以通过删除提示和存在检查来改善此问题。 INSERT查询可以包含FROMWHERE子句,这意味着您可以编写单个查询以仅插入新行。您可以使用OUTPUT子句返回新行的ID。

首先,您需要在UniqueKey列上添加UNIQUE索引或约束。没有其中之一,这根本不是唯一的。任何人都可以插入重复的值。一个UNIQUE约束实际上会创建一个UNIQUE索引:

CREATE UNIQUE INDEX IX_ RequestIdentities_UniqueKey   
   ON RequestIdentities (UniqueKey);

之后,您可以有条件地使用以下命令插入和检索新ID:

INSERT INTO RequestIdentities (UniqueKey)
    OUTPUT inserted.ID
SELECT @key
FROM RequestIdentities
WHERE NOT EXISTS ( select * 
                   from RequestIdentities
                   where UniqueKey = @key)

查询优化器知道不需要为EXISTS ( SELECT *生成任何结果,因此不会影响性能。

此查询将插入新行并返回新ID。此操作是原子操作(成功或回退),因此不需要显式事务。

您可以使用ExecuteScalar()通过SqlCommand执行此查询。这将返回新的ID,如果没有结果,则返回null,因为没有插入行:

using(var connection=new SqlConnection(connString))
using(var cmd=new SqlCommand(query,connection))
{
    cmd.Parameters.Add("@key",SqlDbType.NVarChar,256).Value=key;
    connection.Open();
    var result = cmd.ExecuteScalar();
    if (result!=null)
    {
        var newID=(long)result;
        //Use the ID
        ...
    }
}

您可以使用C#8的模式匹配语法:

if(result is long newId)
{
   //Use the ID
}

如果此代码过多,则可以使用Dapper之类的微型ORM:

using(var connection=new SqlConnection(connString))
{
    var result=connection.ExecuteScalar(query,new {key=keyValue});
    if (result is long new ID)
    {
        ...
    }
}

Dapper由StackOverflow使用,因此可以保证其性能。

其他优化

如果发现此表的锁太多,则可能的优化方法是使用memory optimized tables。数据库服务器已经在积极地缓冲数据。

内存优化表的真正好处是不同的日志记录,锁定和访问模型。代替锁,而是使用轻量级的内存中闩锁对象。由于数据已经在内存中,因此服务器可以使用不同的运算符和不同类型的索引来检索和修改对象。

This documentation example将内存表用于两个高流量表:

  • 购物车是一个持久的内存表,其数据保留在磁盘上。如果服务器出现故障,则推车将保留。
  • 用户会话是不可持久的内存表。如果服务器出现故障,我们不在乎会话

在这种情况下,该表可能是:

CREATE TABLE [dbo].[RequestIdentities]
(
    [Id] [bigint] IDENTITY(1,1) NOT NULL 
        PRIMARY KEY NONCLUSTERED,[UniqueKey] [nvarchar](256) NULL,[Timestamp] [datetime] NULL
)  
WITH (  
    MEMORY_OPTIMIZED = ON,//Assuming we want to retain the data
    DURABILITY = SCHEMA_AND_DATA);  
go  

ALTER TABLE RequestIdentities  
    ADD CONSTRAINT RequestIdentities_UniqueKey  
    UNIQUE NONCLUSTERED (UniqueKey);  
go  
,

您可以添加一列,其中包含要搜索的字段的hash值。

首先,在表中添加新列:

ALTER TABLE [...]
ADD [UniqueKeyHash] VARBINARY(64);

然后,在其上添加索引:

CREATE INDEX IX_..._UniqueKeyHash ON [...]
(
    [UniqueKeyHash] 
);

填充值:

UPDATE [...]
SET [UniqueKeyHash] =  HASHBYTES('SHA2_512',UniqueKey);

也修改CRUD例程以计算HASH

然后,在搜索中:

DECLARE @UniqueKeyHash VARBINARY(64);

SET @UniqueKeyHash = HASHBYTES('SHA2_512','some value');

SELECT *
FROM [...]
WHERE [UniqueKeyHas] = @UniqueKeyHash;

或者,您可以将列添加为computed and persisted,以跳过对CRUD例程的修改。

我正在不同的地方使用这种搜索-其中之一是在IP地址表中,每个用户登录时都在其中搜索并包含数百万条记录。

如果这对您来说太难了,您可以先在UniqueKey上创建索引。

,
  1. 在很大的桌子上,尤其是在聚集身份索引上,填充因子= 80是胡说八道。您失去了20%的空间!
  2. 如果UniqueKey列为UNIQUE,则添加UNIQUE约束。
  3. [Timestamp]是保留字,请勿将其用作列名。

完成此工作后,可以使用以下查询:

INSERT INTO dbo.RequestIdentities(UniqueKey)
OUTPUT inserted.* INTO ...
SELECT @key
EXCEPT
SELECT UniqueKey
FROM   dbo.RequestIdentities
WHERE  UniqueKey = @Key;

Aand将OUTPUT结果子句返回到客户端应用程序的表中

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

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?