1

我们有一个自动递增的身份列 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()我通读了一些博客/论坛帖子,说Entity Framework 用于在插入后返回自动增量值可能存在问题。

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();
4

3 回答 3

2

不,那是不可能的。一方面,这将是 EF 中的一个严重错误。您不是第一个在其上放置 600 次插入/秒的人。此外,SCOPE_IDENTITY它是明确安全的,并且是推荐的做法。

这些语句适用于您使用 SQL ServerIDENTITY列作为 ID 的情况。

于 2013-09-26T22:10:32.210 回答
0

我承认我不知道 Azure SQL 数据库如何同步唯一的、顺序 ID 的生成,但直觉上它一定很昂贵,尤其是按照您的费率。

如果可以选择非顺序 ID,您可能需要考虑在应用程序级别生成 UUID。我知道这不能回答您的直接问题,但它会提高性能(未经验证)并绕过您的问题。

更新:从头开始,Azure SQL 数据库不是分布式的,它只是从单个主节点复制而来。因此,从密钥的替代方案中无法获得真正的性能提升IDENTITY,并且据推测实例的数量对您的问题并不重要。

于 2013-09-27T09:09:51.197 回答
0

我想你的问题可能在这里:

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。如果插入用户失败,它对我来说并不可靠。你确定这是正确的吗?

于 2013-09-27T13:46:51.017 回答