我们有一个自动递增的身份列 ID 作为我的用户对象的一部分。对于我们刚刚为客户做的一个活动,我们每分钟有多达 600 个注册。这是执行添加的代码块:
using (var ctx = new {{ProjectName}}_Entities())
{
int userId = ctx.Users.Where(u => u.Email.Equals(request.Email)).Select(u => u.Id).SingleOrDefault();
if (userId == 0)
{
var user = new User() { /* Initializing user properties here */ };
ctx.Users.Add(user);
ctx.SaveChanges();
userId = user.Id;
}
...
}
然后我们使用userId
将数据插入到另一个表中。在高负载期间发生的情况是,即使不应该有相同的userId
,也有多行。似乎上面的代码为多次插入返回了相同的标识 (int( 编号。
我通读了一些博客/论坛文章,说实体框架用于在插入后返回自动增量值SCOPE_IDENTITY()
可能存在问题。
他们说一种可能的解决方法是为我熟悉的用户编写插入过程INSERT ... OUTPUT INSERTED.Id
。
还有其他人遇到过这个问题吗?关于如何使用实体框架处理此问题的任何建议?
更新 1:
在进一步分析数据后,我几乎 100% 肯定这就是问题所在。标识列跳过了自动增量值的 48 倍,2727, (2728 missing), 2729,...
另一个表中恰好有 48 个重复项。
似乎 EF 为由于某种原因无法插入的每一行返回了随机标识值。
有人知道这里可能会发生什么吗?
更新 2:
我没有提到的可能重要信息是,这发生在使用 Azure SQL 的 Azure 网站上。事件发生时,我们有 4 个实例在运行。
更新 3:
存储过程:
CREATE PROCEDURE [dbo].[p_ClaimCoupon]
@CampaignId int,
@UserId int,
@Flow tinyint
AS
DECLARE @myCoupons TABLE
(
[Id] BIGINT NOT NULL,
[Code] CHAR(11) NOT NULL,
[ExpiresAt] DATETIME NOT NULL,
[ClaimedBefore] BIT NOT NULL
)
INSERT INTO @myCoupons
SELECT TOP(1) c.Id, c.Code, c.ExpiresAt, 1
FROM Coupons c
WHERE c.CampaignId = @CampaignId AND c.UserId = @UserId
DECLARE @couponCount int = (SELECT COUNT(*) FROM @myCoupons)
IF @couponCount > 0
BEGIN
SELECT *
FROM @myCoupons
END
ELSE
BEGIN
UPDATE TOP(1) Coupons
SET UserId = @UserId, IsClaimed = 1, ClaimedAt = GETUTCDATE(), Flow = @Flow
OUTPUT DELETED.Id, DELETED.Code, DELETED.ExpiresAt, CAST(0 AS BIT) as [ClaimedBefore]
WHERE CampaignId = @CampaignId AND IsClaimed = 0
END
RETURN 0
从同一 EF 上下文中调用如下:
var coupon = ctx.Database.SqlQuery<CouponViewModel>(
"EXEC p_ClaimCoupon @CampaignId, @UserId, @Flow",
new SqlParameter("CampaignId", {{CampaignId}}),
new SqlParameter("UserId", {{userId}}),
new SqlParameter("Flow", {{Flow}})).FirstOrDefault();
不,这是不可能的。首先,这将是EF中的一个严重错误。您不是第一个每秒插入 600 次插入的人。此外,SCOPE_IDENTITY
是明确安全的,是推荐的做法。
这些语句适用于使用 SQL Server IDENTITY
列作为 ID 的情况。
我承认我不知道 Azure SQL 数据库如何同步生成唯一的顺序 ID,但直觉上它一定很昂贵,尤其是按照你的费率。
如果可以选择非顺序 ID,则可能需要考虑在应用程序级别生成 UUID。 我知道这并不能回答您的直接问题,但它会提高性能(未经验证(并绕过您的问题。
更新:暂且指出,Azure SQL 数据库不是分布式的,它只是从单个主节点复制的。 因此,IDENTITY
密钥的替代方法没有真正的性能提升,并且假设实例数对您的问题并不重要。
我认为您的问题可能在这里:
UPDATE TOP(1) Coupons
SET UserId = @UserId, IsClaimed = 1, ClaimedAt = GETUTCDATE(), Flow = @Flow
OUTPUT DELETED.Id, DELETED.Code, DELETED.ExpiresAt, CAST(0 AS BIT) as [ClaimedBefore]
WHERE CampaignId = @CampaignId AND IsClaimed = 0
这将更新它在广告系列中找到的尚未声明所有权的第一条记录的 UserId。如果插入用户失败,它对我来说看起来并不强大。你确定这是正确的吗?