【问题标题】:SQL Server database on Azure UNIQUE KEY constraint crashAzure UNIQUE KEY 约束上的 SQL Server 数据库崩溃
【发布时间】:2015-07-02 06:58:01
【问题描述】:

在昨天启动我们软件的促销活动后,我们的 SQL Server 数据库遇到了一些奇怪的问题。当用户在我们的网站上注册帐户时会发生此错误。当它是低用户流量时,一切似乎都很好并且工作正常,但是一旦流量增加到一次约 10 个注册,一切都会下降。一旦它停止对数据库的每个以下请求,就会引发错误。修复它的唯一方法是重新启动网站(直到下一次崩溃等等)。

这是错误:

违反 UNIQUE KEY 约束“AK_Users_Username”。无法在对象“dbo.Users”中插入重复键。重复键值为(用户名)。该语句已终止。

(即使我们在添加之前检查了用户名)

还发生了另一个错误:

不允许新事务,因为会话中还有其他线程在运行。

这是一段将User 及其License 添加到数据库的代码:

var db = new CBEntities();

var user = db.Users.Add(new User
        {
            Username = model.Username,
            Firstname = model.FirstName,
            Lastname = model.LastName,
            Email = model.Email,
            Password = model.Password,
            EmailConfirmed = 0,
            Country = model.Country,
            EmailSubscribed = model.Newsletter != null && model.Newsletter.Value ? 1 : 0,
            Role = "User",
            SignUpDate = DateTime.UtcNow
        });

db.SaveChanges();

//create a initial trial license for user
var lic = db.Licenses.Add(new License
        {
            ExpirationDate = DateTime.UtcNow.AddDays(21),
            UserID = user.ID,
            Key = "",
            PackageID = coupon.ID,
            Status = (int)LicenseStatus.Active,
            Type = (int)LicenseTypes.TRIAL
        });

db.SaveChanges();
user.LicenseID = lic.ID;

db.SaveChanges();

站点和数据库在 Azure 托管上运行。我们确实将网站扩展到标准计划,它甚至使用了几个具有 2 个内核和 3.5 Gb 内存的实例。它没有解决错误。数据库当前具有 S2 性能的标准服务层。等级。 (它链接到网站)..

当没有高用户流量时,一切正常!

请帮助我们找到解决方案

【问题讨论】:

  • 第一条信息很清楚。您正在尝试在 dbo.Users 的用户名列中添加相同的值。当您说在插入之前检查用户名时,您到底是做什么的?那是您的代码还是简化版本?第二条消息与连接的数量无关,而是与某些东西正在尝试使用连接而其他东西已经在使用它的事实有关。
  • 出于好奇,您的用户是使用电子邮件地址还是用户名登录?
  • 在您的代码中,我没有看到您检查现有用户名的位置?
  • @ughai 在调用上面的代码之前检查现有用户名。

标签: .net sql-server entity-framework azure


【解决方案1】:

您没有在 using 语句中包含 DbContext。应该这样处理:

using (var db = new CBEntities()) {

  var user = db.Users.Add(new User
          {
              Username = model.Username,
              Firstname = model.FirstName,
              Lastname = model.LastName,
              Email = model.Email,
              Password = model.Password,
              EmailConfirmed = 0,
              Country = model.Country,
              EmailSubscribed = model.Newsletter != null && model.Newsletter.Value ? 1 : 0,
              Role = "User",
              SignUpDate = DateTime.UtcNow
          });

  db.SaveChanges();

  //create a initial trial license for user
  var lic = db.Licenses.Add(new License
          {
              ExpirationDate = DateTime.UtcNow.AddDays(21),
              UserID = user.ID,
              Key = "",
              PackageID = coupon.ID,
              Status = (int)LicenseStatus.Active,
              Type = (int)LicenseTypes.TRIAL
          });

  db.SaveChanges();
  user.LicenseID = lic.ID;

  db.SaveChanges();
}

释放DbContext 会关闭与数据库的连接。这可以解释为什么不处理 DbContext 会导致连接过多错误。

当您在网站上几乎没有活动时,连接超时,这解释了为什么您在轻负载下没有错误。

【讨论】:

  • 感谢鲍里斯的回答。但是实体框架在 SaveChanges 方法中不会关闭连接吗? (见这里:msdn.microsoft.com/en-us/library/bb738582.aspx
  • 不,它没有。这里相关的是这一行:“如果对象上下文创建连接,则在释放上下文时始终释放连接。” 这就是你调用var db = new CBEntities();时所做的事情,它将调用db.SaveChanges();时使用现有连接。
  • 谢谢。您能否解释一下这一行(我有点困惑):“它仅在需要时打开连接,例如执行查询或调用 SaveChanges。对象服务然后在操作完成时关闭连接。”谢谢
  • 我也对此感到困惑。有点谷歌搜索结果是:blog.jongallant.com/2012/10/… 原来你不必 Dispose,但不这样做充其量是不可预测的(文章 + cmets 很有趣),
  • 您应该能够通过同时生成 11 个以上的随机用户名并通过此应用程序对其进行单元测试。
【解决方案2】:

您的代码结构如下。

  1. 检查重复的用户名
  2. 添加用户
  3. 将用户保存到数据库
  4. 添加许可证
  5. 将许可证保存到数据库
  6. 将许可证与用户关联

此处的每个操作(例如创建用户、创建许可证和关联许可证)都是单独的原子操作。您应该拥有的是所有这些动作一起应该被认为是原子的。我强烈建议有一个事务边界来考虑用户创建、许可证创建及其关联一个单一的原子单元,而不是单独处理它们。

关于唯一键错误

违反 UNIQUE KEY 约束“AK_Users_Username”。无法在对象“dbo.Users”中插入重复键。重复键值为(用户名)。声明已终止。

在上面的序列中,可以有 2 个不同的连接检查相同的用户名“abc.def”并尝试创建用户。但是,这些请求中只有一个能够在数据库中创建新的用户记录。

第二条记录将因违反唯一键约束而失败。

您可以通过在创建用户之前再次检查用户名来最大限度地减少此类错误。这不会完全消除错误,但会尽量减少此类事件的发生。

彻底消除此类错误

  1. 启动可序列化事务
  2. 再次检查用户名
  3. 进行正常处理...
  4. 将许可证与用户关联
  5. 提交事务

注意:使用 Serializable 范围可能会导致阻塞,因为这是最孤立的范围。

参考文章Serializable

希望对你有帮助

【讨论】:

    【解决方案3】:

    如前面的答案所述,您应该始终将 DbContext 包含在 using 语句中。不仅在这段代码中,而且在你每次使用时。

    但是,唯一键约束问题可能是由各种其他原因引起的。 我们看不到您用于检查现有用户名的代码,但我猜它是用 C# 编写的。当您插入新行时,SQL Server 会为您检查用户名约束。 SQL server中的字符串比较与C#相比有很多不同。

    首先,如果没有在列上指定排序规则,SQL Server 依赖于 DB 的默认排序规则。 SQL Azure 的默认排序规则是 SQL_Latin1_General_CP1_CI_AS。 CI 表示不区分大小写 AS 表示区分重音。 这意味着当两个字符串值仅在大小写上不同时,它们被认为是相等的。所以 user123 等于 USER123 和 User123 等等。

    可能导致问题的另一件事是尾随空格的处理。 SQL Server 基本上是通过逐个比较它们的字符来比较stings。如果一个字符串比另一个短,它会在较短的字符串的末尾添加额外的空格以使其长度相等。这样'User123' 等于'User123 ' 以及任何以User123 开头并以多个空格结尾的字符串。这意味着您应该始终在保存用户名之前对其进行修剪。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多