Linq to sql 高负载页面上LINQ到SQL的替代方案

Linq to sql 高负载页面上LINQ到SQL的替代方案,linq-to-sql,Linq To Sql,首先,我喜欢LINQtoSQL。它比直接查询更容易使用 但是,有一个很大的问题:它不能很好地处理高负载请求。我的ASP.NET MVC项目中有一些操作,每分钟调用数百次 我曾经在那里使用LINQtoSQL,但由于请求量巨大,LINQtoSQL几乎总是返回“未找到或更改行”或“X个更新失败”。这是可以理解的。例如,我必须在每个请求中增加一个值 var stat = DB.Stats.First(); stat.Visits++; // .... DB.SubmitChanges(); 但是当AS

首先,我喜欢LINQtoSQL。它比直接查询更容易使用

但是,有一个很大的问题:它不能很好地处理高负载请求。我的ASP.NET MVC项目中有一些操作,每分钟调用数百次

我曾经在那里使用LINQtoSQL,但由于请求量巨大,LINQtoSQL几乎总是返回“未找到或更改行”或“X个更新失败”。这是可以理解的。例如,我必须在每个请求中增加一个值

var stat = DB.Stats.First();
stat.Visits++;
// ....
DB.SubmitChanges();
但是当ASP.NET正在处理这些//。。。说明,表中存储的stats.visions值已更改

我找到了一个解决方案,我创建了一个存储过程

更新统计数据集访问次数=访问次数+1

它工作得很好

不幸的是,现在我有越来越多这样的时刻。为所有情况创建存储过程都很糟糕

所以我的问题是,如何解决这个问题?这里有其他可行的方案吗


我听说Stackoverflow与LINQtoSQL一起工作。而且它的负载比我的站点更大。

这并不完全是Linq to SQL的问题,从本质上来说,这是一个乐观并发的预期结果,Linq to SQL默认使用乐观并发

乐观并发意味着在更新记录时,在进行任何脱机更新之前,要对照最初检索到的副本检查数据库中的当前版本;如果它们不匹配,则报告并发冲突(“未找到或更改行”)

对此有更详细的解释。还有一个相当大的问题。通常,解决方案只需捕获
ChangeConflictException
并选择解决方案,例如:

try
{
    // Make changes
    db.SubmitChanges();
}
catch (ChangeConflictException)
{
    foreach (var conflict in db.ChangeConflicts)
    {
        conflict.Resolve(RefreshMode.KeepCurrentValues);
    }
}
上述版本将用当前值覆盖数据库中的任何内容,而不管是否进行了其他更改。有关其他可能性,请参见枚举

您的另一个选择是完全禁用乐观并发性,以用于预期可能会更新的字段。您可以通过将
UpdateCheck
选项设置为
UpdateCheck.Never
来实现这一点。这必须在实地一级进行;您不能在实体级别或全局上下文级别执行此操作

也许我还应该提到,对于您试图解决的特定问题,您没有选择一个非常好的设计。通过重复更新单行中的一列来增加“计数器”不是关系数据库的一种很好/合适的用法。实际上,您应该做的是维护一个历史记录表,例如
访问量
,如果确实需要对计数进行反规范化,请在数据库本身中使用触发器来实现。试图在没有任何数据备份的情况下在应用程序级别实现站点计数器只是自找麻烦


使用您的应用程序将实际数据放入数据库,并让数据库处理聚合—这是数据库擅长的事情之一。

使用生产者/消费者或消息队列模型进行不必立即进行的更新,尤其是状态更新。不要试图立即更新数据库,而是保留一个asp.net线程可以推送到的更新队列,然后有一个写入进程/线程将该队列写入数据库。因为只有一个线程在写,所以在相关表/角色上的争用会少很多


对于读取,使用缓存。对于高容量站点,甚至缓存数据几秒钟都会产生影响。

首先,您可以在
stats.visions++
之后立即调用
DB.SubmitChanges()
,这将大大减少问题

但是,这仍然无法避免并发冲突(即,通过两个并发进程同时修改一段数据)。为了解决这个问题,您可以使用标准的事务机制。使用LINQ to SQL,您可以通过实例化
TransactionScope
类来使用事务,因此:

using( TransactionScope t = new TransactionScope() )
{
    var stats = DB.Stats.First();
    stats.Visits++;
    DB.SubmitChanges();
}
更新:正如Aaronaught正确指出的,TransactionScope实际上在这方面没有帮助。很抱歉但请继续读下去

不过,要小心,不要让事务主体太长,因为它会阻塞其他并发进程,从而显著降低整体性能

这就引出了下一点:你的设计很可能有缺陷

处理高度共享数据的核心原则是以这样的方式设计应用程序:对该数据的操作快速、简单且语义清晰,并且必须一个接一个地执行,而不是同时执行

您正在描述的一个操作—计算访问次数—非常清楚和简单,因此,一旦添加了事务,应该不会有问题。但是,我必须补充一点,虽然这将是清晰的、类型安全的,而且在其他方面“很好”,但使用存储过程的解决方案实际上是一个更受欢迎的解决方案。事实上,这正是过去数据库应用程序的设计方式。想一想:如果处理计数器时没有涉及业务逻辑,那么为什么需要从数据库到应用程序(可能是通过网络!)一路获取计数器呢。数据库服务器也可以对其进行增量,甚至不向应用程序发送任何内容


现在,对于隐藏在
/…
后面的其他操作来说,(根据您的描述)它们似乎有些沉重和冗长。我不能确定,因为我不知道那里有什么,但如果是这样的话,你可能想把它们分成更小更快的,或者重新考虑你的设计。有了这一点信息,我真的说不出其他任何事情。

但是这个解决方案不适用于计算访问量的特殊问题:无论您采用何种解决策略,您最终都会损失(即不计算)一些访问量。@Fyodor: