我有一个包含约50M行的表(UrlLog)。少数应用程序实例每分钟在此表中总共插入4000-5000个新行。每天晚上都会运行一个作业,删除该行中的每个记录,其中主键未被其他两个表中的任何行引用(这两个表包含大约50M(OutLog)和150M(InLog)行)。
我在清理过程正在运行时,在访问此表的任何操作上选择,删除和插入干扰并导致超时时遇到了一些麻烦。
以下是表格,基本上是:
CREATE TABLE UrlLog (
Id BIGINT NOT NULL PRIMARY KEY,
Hash UNIQUEIDENTIFIER NOT NULL,
Protocol TINYINT NOT NULL,
DomainId SMALLINT NOT NULL,
Path NVARCHAR(4000) NOT NULL,
Query NVARCHAR(4000) NOT NULL,
UNIQUE INDEX IX_UrlLog_Hash NONCLUSTERED (Hash)
)
CREATE TABLE InLog (
Id BIGINT NOT NULL PRIMARY KEY,
UrlId BIGINT NOT NULL,
Timestamp INT NOT NULL,
ResponseTime REAL NOT NULL,
IpAddress INT NOT NULL,
ErrorId INT NOT NULL,
Flags TINYINT NOT NULL,
INDEX IX_InLog_UrlId NONCLUSTERED (UrlId)
)
CREATE TABLE OutLog (
Id BIGINT NOT NULL PRIMARY KEY,
UrlId BIGINT NOT NULL,
ApiId SMALLINT NOT NULL,
Timestamp INT NOT NULL,
ResponseTime REAL NOT NULL,
HttpStatus TINYINT NOT NULL,
ErrorId INT NOT NULL,
INDEX IX_OutLog_UrlId NONCLUSTERED (UrlId)
)
通过以下步骤进行插入。每个应用程序实例每分钟刷新缓冲的记录。每分钟都有800-1200个新行发送到此插入过程,并且它们是批量提交的 - 目前一次有400行通过表值参数发送到存储过程。发送到此过程的大多数行都是新的,并导致表插入。
主键ID在应用程序中生成,而不是在DB中自动增量。 ID将返回到应用程序,因此它可以缓存这些日志值并关联将来的重复项,而无需再次查询数据库。每分钟大约一半的UrlLog行是新的,大约一半已经在应用程序内存中。我们还假设Hash是无碰撞的。在非常罕见的碰撞情况下,可以使用不正确的Url关联。
CREATE TYPE [dbo].[UrlInsertTableType] AS TABLE (
Id BIGINT NOT NULL,
Hash UNIQUEIDENTIFIER NOT NULL,
Protocol TINYINT NOT NULL,
DomainId SMALLINT NOT NULL,
Path NVARCHAR(4000) NOT NULL,
Query NVARCHAR(4000) NOT NULL)
CREATE PROCEDURE [dbo].[LogUrls]
@Urls [dbo].[UrlInsertTableType] READONLY
AS
SET NOCOUNT ON
DECLARE @FINAL_ID BIGINT
DECLARE @ID BIGINT
DECLARE @HASH UNIQUEIDENTIFIER
DECLARE @PROTOCOL TINYINT
DECLARE @DOMAINID SMALLINT
DECLARE @PATH NVARCHAR(4000)
DECLARE @QUERY NVARCHAR(4000)
CREATE TABLE #UrlInsertTemp (
Id BIGINT NOT NULL,
Hash UNIQUEIDENTIFIER NOT NULL
)
BEGIN TRAN
DECLARE CUR CURSOR LOCAL FAST_FORWARD FOR
SELECT Id, Hash, Protocol, DomainId, Path, Query FROM @Urls
OPEN CUR
WHILE 1=1
BEGIN
FETCH NEXT FROM CUR INTO @ID, @HASH, @PROTOCOL, @DOMAINID, @PATH, @QUERY
IF @@FETCH_STATUS = -1 BREAK
SET @FINAL_ID = NULL
SELECT @FINAL_ID = Id FROM Url WHERE Hash=@HASH
IF @FINAL_ID IS NULL BEGIN
INSERT INTO Url (Id, Hash, Protocol, DomainId, Path, Query)
VALUES (@ID, @HASH, @PROTOCOL, @DOMAINID, @PATH, @QUERY)
SELECT @FINAL_ID = @ID
END
INSERT INTO #UrlInsertTemp (Id, Hash) VALUES (@FINAL_ID, @HASH)
END
CLOSE CUR
DEALLOCATE CUR
COMMIT
SELECT Id, Hash AS [Key] FROM #UrlInsertTemp
通过以下过程进行删除。应用程序代码在循环中调用该过程,直到它到达最大的Url.Id.
CREATE PROCEDURE [dbo].[DeleteUrls]
@LastId BIGINT
AS
SET NOCOUNT ON
DECLARE @ID BIGINT
DECLARE @FOUND BIGINT
BEGIN TRAN
DECLARE CUR CURSOR LOCAL FAST_FORWARD FOR
SELECT TOP 200 Id FROM UrlLog WHERE Id > @LastId ORDER BY Id
OPEN CUR
WHILE 1=1
BEGIN
FETCH NEXT FROM CUR INTO @ID
IF @@FETCH_STATUS = -1 BREAK
SELECT @FOUND = Id FROM InLog WHERE UrlId=@ID
IF @FOUND IS NULL BEGIN
SELECT @FOUND = Id FROM OutLog WHERE UrlId=@ID
IF @FOUND IS NULL BEGIN
DELETE FROM UrlLog WHERE Id=@ID
END
END
END
CLOSE CUR
DEALLOCATE CUR
COMMIT
SELECT @ID
应用程序调用伪代码:
topId = GetLargestUrlLogId()
lastId = 0
while (lastId < topId)
lastId = executeDeleteUrlProcedure(lastId)
有关如何更改删除或插入行的方式以帮助它们更好地一起播放的任何建议?
答案 0 :(得分:1)
我有几个想法,过去曾为我效劳过。我发现在处理这种高度交易时,“科学”中有很多“艺术”,对某人有用,对其他人来说并不适用。希望其中一个想法能够解决您的问题。
尝试制作需要删除的队列表。使用SELECT和WITH(NOLOCK)填充一个只有1列的表,该列是要清理的表的主键。然后尝试加入'chunk'删除(我已经取得了一些成功)。还可以尝试简单地删除TOP 1并加入表并循环(出于某种原因,这里会有更好的成功。)我的调查结果表明,删除不是导致争用,而是查找是什么删除导致更大的问题。这些方法很奇怪,因为很多小交易都是我们一直被教导的错误!
在清洁开始之前,请更改清洁时日志的位置转到“保留”表。清理完成后,重新定向记录并导入“保留”数据。
完全暂停日志记录。做1巨大删除,恢复清理。这可能不是一个选择。
轮换您的日志记录。每天都要去一张新桌子。使用视图组合表格进行阅读。要删除数据,只需删除最旧的表格。
考虑是否有其他因素可以简单地提高整体表现?也许将数据库事务模型更改为简单或更改隔离级别?