锁定表以在 LINQ 中获取 MAX
我在 LINQ 中有一个查询,我想获取表的最大代码并增加它并使用新代码插入新记录。就像 SQL Server 的 IDENTITY 功能一样,但这里我的 Code 列是 char(5),其中可以是字母和数字。
我的问题是,当插入新行时,两个并发进程获得最大值并向记录插入相等的代码。
我的命令是:
var maxCode = db.Customers.Select(c=>c.Code).Max();
var anotherCustomer = db.Customers.Where(...).SingleOrDefault();
anotherCustomer.Code = GenerateNextCode(maxCode);
db.SubmitChanges();
我跨 1000 个线程运行此命令,每个线程更新 200 个客户,并使用具有 IsolationLevel.Serialized 的事务,在执行两到三次后发生错误:
using (var db = new DBModelDataContext())
{
DbTransaction tran = null;
try
{
db.Connection.Open();
tran = db.Connection.BeginTransaction(IsolationLevel.Serializable);
db.Transaction = tran;
.
.
.
.
tran.Commit();
}
catch
{
tran.Rollback();
}
finally
{
db.Connection.Close();
}
}
错误:
事务(进程 ID 60)是 锁资源死锁 另一个过程并被选为 僵局受害者。重新运行 交易。
其他 IsolationLevel 会生成此错误:
未找到或更改行。
请帮助我,谢谢。
UPDATE2:我有一个生成新代码的.NET 方法,该代码是字母数字的。 UPDATE3:我的.NET函数生成如下代码: 0000, 0001, 0002, ... , 0009, 000a, 000b, 000c, ... , 000z, 0010, 0011, 0012, ... , 0019, 001a, 001b 、001z、……
I have a query in LINQ, I want to get MAX of Code of my table and increase it and insert new record with new Code. just like the IDENTITY feature of SQL Server, but here my Code column is char(5) where can be alphabets and numeric.
My problem is when inserting a new row, two concurrent processes get max and insert an equal Code to the record.
my command is:
var maxCode = db.Customers.Select(c=>c.Code).Max();
var anotherCustomer = db.Customers.Where(...).SingleOrDefault();
anotherCustomer.Code = GenerateNextCode(maxCode);
db.SubmitChanges();
I ran this command cross 1000 threads and each updating 200 customers, and used a Transaction with IsolationLevel.Serializable, after two or three execution an error occured:
using (var db = new DBModelDataContext())
{
DbTransaction tran = null;
try
{
db.Connection.Open();
tran = db.Connection.BeginTransaction(IsolationLevel.Serializable);
db.Transaction = tran;
.
.
.
.
tran.Commit();
}
catch
{
tran.Rollback();
}
finally
{
db.Connection.Close();
}
}
error:
Transaction (Process ID 60) was
deadlocked on lock resources with
another process and has been chosen as
the deadlock victim. Rerun the
transaction.
other IsolationLevels generates this error:
Row not found or changed.
Please help me, thank you.
UPDATE2: I have a .NET method generating the new code, which is alphanumeric.
UPDATE3: My .NET function generates code like this: 0000, 0001, 0002, ... , 0009, 000a, 000b, 000c, ... , 000z, 0010, 0011, 0012, ... , 0019, 001a, 001b, 001z, ... ...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
避免锁定和连续访问相同的“慢速访问”资源:
避免使用 IDENTITY 列(如果事务回滚,id 仍将递增)
使用某个表来存储每个表(或所有表)的键(最后一个或下一个 id)变体)。
运气。
对于您的 Web 应用程序:
如何更改和访问应用程序状态:
第二种解决方案:
使用存储过程和代码,如下所示:
更新操作是原子的,您可以增加 id 并返回当前的 id。
不要忘记为每个表的 ID 插入第一条也是唯一一条记录。
第三种解决方案:
使用CLR函数。
Avoid locking and continuous access to the same "slow access" resources:
Avoid using IDENTITY columns (if transaction rollbacks the id will be still incremented)
Use some table to store keys (last or next ids) for every table (or for the all tables as variant).
Luck.
For your web application:
How to change and access Application state:
Second solution:
Use stored procedure and code some like this:
Update operation is atomic and you can increment id and return current one.
Don't forget to insert the first and only record for every table's ids.
Third solution:
Use CLR function.
如果可能的话,尝试重新考虑您的数据库设计。正如您已经注意到的,在锁定整个表时必须使用隔离级别
Serialized
可能会很麻烦。我假设 5 个字符的唯一递增值是必需的,因为如果不是这样,您绝对应该简单地使用 IDENTITY 列。然而,假设情况并非如此,这里有一个可能可行的想法。
尝试创建一个方法,允许将 5 个字符的标识符表示为数字。如何执行此操作取决于 char 标识符中允许使用哪些字符以及可能的组合,但这里有一些示例: '00000' -> 0, '00009', ->; 9、'0000z'-> 36、'00010'-> 37、'0001z'-> 71、'zzzzz'-> 60466175. 找到方法后,请对表使用递增主键,并使用在插入记录后计算字符标识符的触发器。当触发器不合适时,您也可以在 .NET 中执行此操作。或者您可以选择不在数据库中存储该 5 个字符的值,因为它是经过计算的。您可以将其定义在视图中或简单地定义为域实体中的属性。
If possible, try to rethink your database design. As you already noticed, having to use isolation level
Serializable
, when locking a whole table, can be troublesome.I assume that the 5 character unique incrementing value is a requirement, because when it is not, you should definitely simply use an IDENTITY column. However, assuming this is not the case, here is an idea that might work.
Try to create a method that allows to express that 5 char identifier as a number. How to do this depends on which characters are allowed in your char identifier and which combinations are possible, but here are some examples: '00000' -> 0, '00009', -> 9, '0000z' -> 36, '00010' -> 37, '0001z' -> 71, 'zzzzz' -> 60466175. When you've found a method, use a incrementing primary key for the table and use a trigger that calculates the char identifier after you inserted a record. When a trigger is not appropriate, you can also do this in .NET. Or you can choose not to store that 5 char value in your database, because it is calculated. You can define it in a view or simply as property in your domain entity.
我有一个解决方案,但不完整,它减少了错误和问题:我有一个名为:“lock.txt”的文件,每次尝试获取锁定都应打开此文件,获取最大代码并生成下一个并更新我的表并关闭文件。该文件只是用于打开和关闭,里面没有任何内容。
请说明这个解决方案是否正确。
或者使用 StreamWriter。
I have a solution, but not complete, It reduces the errors and problems: I have a file named: "lock.txt" and every try to get lock should open this file, get maximum code and generate next and update my table and close the file. the file is just for opening and closing, and there is no content in it.
Please say if this solution is correct.
Or use StreamWriter.
查看您的函数GenerateNextCode 非常有用,因为它可能是至关重要的信息。为什么?因为我不相信不可能将此功能从 更改
为
如果后者为真,您可以重新设计您的表,整个概念会容易得多。
但假设这确实不可能,有一些快速的解决方案——使用带有预生成代码的台球表。然后在主表中使用简单的 ids(自动增量)。
缺点:您必须使用额外的联接来检索数据。我个人不喜欢它。
另一种解决方案,“正常”解决方案:保持较低的隔离级别并简单地处理异常(即再次获取代码,再次计算新代码并保存数据)。这是非常经典的情况,有网络,没有网络,都没关系。
请注意:并发编辑相同数据时您会遇到相同的问题。所以从某种意义上来说,你无法避免这种问题。
编辑:
所以,我猜对了,这个函数只是 f(id) ->代码。您可以删除代码列并使用自动增量 id。然后添加一个视图,在其中动态计算代码。使用视图作为从表中检索数据的方式始终是一个好主意(将其视为 C# 中属性的 getter)。
如果您担心 CPU 使用率;-) 您可以在插入记录时计算代码(使用触发器)。
当然,锁定记录的问题并没有完全消除(仍然可能发生并发编辑)。
It would be really useful to see your function GenerateNextCode, because it could be crucial piece of information. Why? Because I don't believe it is not possible to change this function from
to
If the latter is true, you could redesign your table and whole concept would be much easier.
But assuming it is really not possible, some quick solution -- use the pool table with pregenerated codes. Then use simply ids (autoincremented) in your main table.
Disadvantage: you have to use extra join to retrieve the data. Personally I don't like it.
Another solution, "normal" one: keep lower isolation level and simply handle the exception (i.e. get the code again, calculate new code again and save the data). It is pretty classic situation, web, no web, does not matter.
Please note: you will get the same problem on concurrent editing of the same data. So in some sense, you cannot avoid this kind of problem.
EDIT:
So, I guessed right this function is simply f(id) -> code. You can drop the code column and use autoincrement id. Then add a view where the code is calculated on fly. Using view as a way of retrieving the data from the table is always a good idea (think of it as getter of property in C#).
If you are afraid of CPU usage ;-) you can calculate code while inserting records (use the triggers).
Of course problems with locking records are not removed entirely (concurrent edits still can occur).
这是我的答案,不完全正确,但没有错误。
here is my answer, not completely correct, but without error.