插入时违反PRIMARY KEY约束

本文关键字:KEY 约束 PRIMARY 插入 | 更新日期: 2023-09-27 17:58:18

我有一个简单的表:

IPAddress (PK, string)
Requests (int)

这是一个洪水限制器。每分钟都会删除表数据。每个页面请求,对于给定的IPAddressRequests计数递增。

它工作得很好,我们的网站性能显著提高,因为我们的产品和网站的性质确实导致了一些意外/故意的有效DDOS。

唯一的问题是,当一个IP出于任何原因每分钟向我们的网站发送数千个请求时,我们会弹出以下错误:

违反PRIMARY KEY约束"PK_v2SiteIPRequests"。无法在对象"dbo.v2SiteIPRequests"中插入重复的密钥。重复的密钥值为([IP_ADDRESS])。语句已终止。

插入的代码是:

/// <summary>
/// Call everytime a page view is requested
/// </summary>
private static void DoRequest(string ipAddress)
{
    using (var db = new MainContext())
    {
        var rec = db.v2SiteIPRequests.SingleOrDefault(c => c.IPAddress == ipAddress);
        if (rec == null)
        {
            var n = new v2SiteIPRequest {IPAddress = ipAddress, Requests = 1};
            db.v2SiteIPRequests.InsertOnSubmit(n);
            db.SubmitChanges();
        }
        else
        {
            rec.Requests++;
            db.SubmitChanges();
            // Ban?
            if (rec.Requests >= Settings.MAX_REQUESTS_IN_INTERVAL)
            {
                BanIP(ipAddress);
            }
        }
    }
}

处理这个异常的最佳方法是什么?为什么要抛出它?try catch在这里最好吗?

插入时违反PRIMARY KEY约束

如果同时收到两个请求,则会发生以下情况:

Request one: is it in the database?
Request two: is it in the database?
Request one: No, not yet
Request two: No, not yet
Request one: INSERT
Request two: INSERT
Request one: WORKS
Request two: FAILS (already inserted a split second before)

在这里,除了捕获异常并优雅地处理它之外,您别无选择。也许可以用一个简单的"再试一次"逻辑。

这里有一些竞争条件,尤其是当存在并发连接时。

您可能需要更改方法,并始终存储每个请求,然后查询时间范围内是否有超出允许的请求,并采取您需要的任何操作

以下是基于建议的解决方案。它很难看,但据我所知是有效的。

/// <summary>
/// Call everytime a page view is requested
/// </summary>
private static void DoRequest(string ipAddress)
{
    using (var db = new MainContext())
    {
        var rec = db.v2SiteIPRequests.SingleOrDefault(c => c.IPAddress == ipAddress);
        if (rec == null)
        {
            // Catch insert race condition for PK violation.  Especially susceptible when being hammered by requests from 1 IP
            try
            {
                var n = new v2SiteIPRequest {IPAddress = ipAddress, Requests = 1};
                db.v2SiteIPRequests.InsertOnSubmit(n);
                db.SubmitChanges();
            }
            catch (Exception e)
            {
                try
                {
                    // Can't reuse original context as it caches
                    using (var db2 = new MainContext())
                    {
                        var rec2 = db2.v2SiteIPRequests.Single(c => c.IPAddress == ipAddress);
                        rec2.Requests++;
                        db2.SubmitChanges();
                        if (rec2.Requests >= Settings.MAX_REQUESTS_IN_INTERVAL)
                        {
                            BanIP(ipAddress);
                        }
                    }
                }
                catch (Exception ee)
                {
                    // Shouldn't reach here
                    Error.Functions.NewError(ee);
                }
            }
        }
        else
        {
            rec.Requests++;
            db.SubmitChanges();
            // Ban?
            if (rec.Requests >= Settings.MAX_REQUESTS_IN_INTERVAL)
            {
                BanIP(ipAddress);
            }
        }
    }
}