【发布时间】:2017-11-20 13:24:32
【问题描述】:
我创建了一个存储过程来实现对我的 API 的速率限制,这大约每秒调用 5-10k 次,并且每天我都会注意到计数器表中的欺骗。
它查找传入的 API 密钥,然后使用“UPSERT”检查带有 ID 和日期组合的计数器表,如果找到结果,它会执行 UPDATE [count]+1,如果没有,它将插入新行。
计数器表中没有主键。
这是存储过程:
USE [omdb]
GO
/****** Object: StoredProcedure [dbo].[CheckKey] Script Date: 6/17/2017 10:39:37 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[CheckKey] (
@apikey AS VARCHAR(10)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @userID as int
DECLARE @limit as int
DECLARE @curCount as int
DECLARE @curDate as Date = GETDATE()
SELECT @userID = id, @limit = limit FROM [users] WHERE apiKey = @apikey
IF @userID IS NULL
BEGIN
--Key not found
SELECT 'False' as [Response], 'Invalid API key!' as [Reason]
END
ELSE
BEGIN
--Key found
BEGIN TRANSACTION Upsert
MERGE [counter] AS t
USING (SELECT @userID AS ID) AS s
ON t.[ID] = s.[ID] AND t.[date] = @curDate
WHEN MATCHED THEN UPDATE SET t.[count] = t.[count]+1
WHEN NOT MATCHED THEN INSERT ([ID], [date], [count]) VALUES (@userID, @curDate, 1);
COMMIT TRANSACTION Upsert
SELECT @curCount = [count] FROM [counter] WHERE ID = @userID AND [date] = @curDate
IF @limit IS NOT NULL AND @curCount > @limit
BEGIN
SELECT 'False' as [Response], 'Request limit reached!' as [Reason]
END
ELSE
BEGIN
SELECT 'True' as [Response], NULL as [Reason]
END
END
END
我也认为在引入这个SP之后会发生一些锁定。
骗子并没有破坏任何东西,但我很好奇我的代码是否存在根本性的问题,或者我是否应该在表中设置一个约束来防止这种情况发生。谢谢
2017 年 6 月 23 日更新:我删除了 MERGE 语句并尝试使用 @@ROWCOUNT 但它也造成了欺骗
BEGIN TRANSACTION Upsert
UPDATE [counter] SET [count] = [count]+1 WHERE [ID] = @userID AND [date] = @curDate
IF @@ROWCOUNT = 0 AND @@ERROR = 0
INSERT INTO [counter] ([ID], [date], [count]) VALUES (@userID, @curDate, 1)
COMMIT TRANSACTION Upsert
【问题讨论】:
-
我不会为此使用 MERGE 语句。在这种情况下,它隐藏了意图。使用显式事务和单独的选择/插入/更新
-
@MitchWheat 我希望使用 SQL 新引入的“UPSERT”方法比执行 UPDATE 和检查 @@rowcount 更有效,但似乎恰恰相反。
-
为什么不能将id设置为表的唯一键
-
@SagarV 因为计数器在同一个表中跟踪多天。
-
但我认为 ID 应该是唯一的
标签: sql-server stored-procedures counter upsert