一些背景资料:
通过删除旧数据并插入新数据来定期更新数据。
数据被聚集到配置文件中,我将其用作主键的一部分并删除旧数据
只有一个进程写入数据,因此不必担心其他人的更新冲突
其他过程只读取我计划通过Snapshot-Isolation解决的数据
我通过实体框架6访问数据,代码优先建模。
问题: 我开始收集几个并行的配置文件的数据。适用于具有简单外键关系的所有表,但对于具有自引用的表。
public class Web
{
public Web()
{
CacheDate = DateTime.Now;
}
[Key, Column(Order = 0)]
public Guid ProfileGuid { get; set; }
[Key, Column(Order = 2)]
public Guid WebId { get; set; }
public string Data { get; set; }
public virtual List<List> Lists { get; set; }
public virtual List<Web> SubWebs { get; set; }
public Guid? ParentProfileGuid { get; set; }
public Guid? ParentWebId { get; set; }
public virtual Web ParentWeb { get; set;}
}
modelBuilder.Entity<CacheWeb>()
.HasMany(e => e.SubWebs)
.WithOptional(e => e.ParentWeb)
.HasForeignKey(e => new { e.ParentProfileGuid, e.ParentWebId })
.WillCascadeOnDelete(false);
由于性能问题,我不使用EF删除数据,而是使用自定义sql命令:InsertContext.Database.ExecuteSqlCommand(String.Format("DELETE FROM Webs WHERE ProfileGuid = '{0}'", profile.Guid));
如果我在并发事务中调用其中两个删除,则第一个调用会创建一个锁,以防止第二个事务继续。
据我所知,锁定以某种方式连接到自引用的索引。
我刚发现另一个奇怪的行为,它取决于首先删除哪个事务。 我能找到的数据的唯一区别是亲子关系的深度 如果我首先运行事务A(有一个父母和几个直接子女)我之后不能运行事务B(有一些父 - >孩子 - >子女关系),但如果我先运行B,我可以在之后运行A遇到现有锁的问题。
有没有办法解决这个问题?或者任何想法可能是锁的问题? 如果您需要更多信息,请评论您需要什么,因为我不确定什么是重要的。
编辑:澄清
BEGIN Transaction t1
DELETE FROM Webs WHERE ProfileGuid = 'b35dbba4-54fc-4df7-b1c8-e559d81dfee3'
BEGIN Transaction t2
DELETE FROM Webs WHERE ProfileGuid = 'b35dbba4-54fc-4df7-b1c8-e559d81dfee4'
的工作原理。交换订单,t1必须等待t2完成。
EDIT3:模型和完整查询
执行计划来自第3个查询(删除CacheList)
BEGIN Transaction t1
DELETE FROM CacheItems WHERE ProfileGuid = 'B35DBBA4-54FC-4DF7-B1C8-E559D81DFEE3'
DELETE FROM CacheFolders WHERE ProfileGuid = 'B35DBBA4-54FC-4DF7-B1C8-E559D81DFEE3'
DELETE FROM CacheLists WHERE ProfileGuid = 'B35DBBA4-54FC-4DF7-B1C8-E559D81DFEE3'
DELETE FROM CacheWebs WHERE ProfileGuid = 'B35DBBA4-54FC-4DF7-B1C8-E559D81DFEE3'
答案 0 :(得分:0)
SQL Server可以根据每个特定查询锁定不同的级别。这可以解释为什么更改订单可以使交易锁定另一个或不锁定。
避免锁定的方法是
这两种解决方案都很危险,因为较低的隔离级别可能会导致各种错误。
答案 1 :(得分:0)
关键是在事务之间实现完全隔离。也就是说,两个事务不需要触及相同的记录。
将记录划分为并行化组时,共享id的记录应位于同一组中。一个简单的Parallel.Foreach是不够的。可以按顺序处理每个组内的数据,而无需与另一个并行组共享任何数据。
除了应用程序中的逻辑隔离外,您还需要确保在索引节点级别将不同的查询彼此隔离。确保所有查询都使用索引查找而不是扫描。这将避免两个查询在搜索记录时将S锁(共享锁)放在同一记录上,然后在尝试删除时无法将其转换为X锁。使用“显示实际执行计划”运行每个查询,并确保没有看到索引扫描。如果您看到扫描并且不明白为什么然后发表评论。