Deadlock on SELECT/UPDATE - sql-server-2008

I'm having a problem with deadlock on SELECT/UPDATE on SQL Server 2008.
I read answers from this thread: SQL Server deadlocks between select/update or multiple selects but I still don't understand why I get deadlock.
I have recreated the situation in the following testcase.
I have a table:
CREATE TABLE [dbo].[SessionTest](
[SessionId] UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL,
[ExpirationTime] DATETIME NOT NULL,
CONSTRAINT [PK_SessionTest] PRIMARY KEY CLUSTERED (
[SessionId] ASC
) WITH (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[SessionTest]
ADD CONSTRAINT [DF_SessionTest_SessionId]
DEFAULT (NEWID()) FOR [SessionId]
GO
I'm trying first to select a record from this table and if the record exists set expiration time to current time plus some interval. It is accomplished using following code:
protected Guid? GetSessionById(Guid sessionId, SqlConnection connection, SqlTransaction transaction)
{
Logger.LogInfo("Getting session by id");
using (SqlCommand command = new SqlCommand())
{
command.CommandText = "SELECT * FROM SessionTest WHERE SessionId = #SessionId";
command.Connection = connection;
command.Transaction = transaction;
command.Parameters.Add(new SqlParameter("#SessionId", sessionId));
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.Read())
{
Logger.LogInfo("Got it");
return (Guid)reader["SessionId"];
}
else
{
return null;
}
}
}
}
protected int UpdateSession(Guid sessionId, SqlConnection connection, SqlTransaction transaction)
{
Logger.LogInfo("Updating session");
using (SqlCommand command = new SqlCommand())
{
command.CommandText = "UPDATE SessionTest SET ExpirationTime = #ExpirationTime WHERE SessionId = #SessionId";
command.Connection = connection;
command.Transaction = transaction;
command.Parameters.Add(new SqlParameter("#ExpirationTime", DateTime.Now.AddMinutes(20)));
command.Parameters.Add(new SqlParameter("#SessionId", sessionId));
int result = command.ExecuteNonQuery();
Logger.LogInfo("Updated");
return result;
}
}
public void UpdateSessionTest(Guid sessionId)
{
using (SqlConnection connection = GetConnection())
{
using (SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.Serializable))
{
if (GetSessionById(sessionId, connection, transaction) != null)
{
Thread.Sleep(1000);
UpdateSession(sessionId, connection, transaction);
}
transaction.Commit();
}
}
}
Then if I try to execute test method from two threads and they try to update same record I get following output:
[4] : Creating/updating session
[3] : Creating/updating session
[3] : Getting session by id
[3] : Got it
[4] : Getting session by id
[4] : Got it
[3] : Updating session
[4] : Updating session
[3] : Updated
[4] : Exception: Transaction (Process ID 59) was deadlocked
on lock resources with another process and has been
chosen as the deadlock victim. Rerun the transaction.
I can't understand how it can happen using Serializable Isolation Level. I think first select should lock row/table and won't let another select to obtain any locks. The example is written using command objects but it's just for test purposes. Originally, i'm using linq but I wanted to show simplified example. Sql Server Profiler shows that deadlock is key lock. I will update the question in few minutes and post graph from sql server profiler. Any help would be appreciated. I understand that solution for this problem may be creating critical section in code but I'm trying to understand why Serializable Isolation Level doesn't do the trick.
And here is the deadlock graph:
deadlock http://img7.imageshack.us/img7/9970/deadlock.gif
Thanks in advance.

Its not enough to have a serializable transaction you need to hint on the locking for this to work.
The serializable isolation level will still usually acquire the "weakest" type of lock it can which ensures the serializable conditions are met (repeatable reads, no phantom rows etc)
So, you are grabbing a shared lock on your table which you are later (in your serializable transaction) trying to upgrade to an update lock. The upgrade will fail if another thread is holding the shared lock (it will work if no body else it holding a shared lock).
You probably want to change it to the following:
SELECT * FROM SessionTest with (updlock) WHERE SessionId = #SessionId
That will ensure an update lock is acquired when the SELECT is performed (so you will not need to upgrade the lock).

Related

executeUpdate in createQuery does not update my database

public void updateUserState(User user) {
Session sess=getSession();
sess.setFlushMode(FlushMode.MANUAL);
String queryStr = "update User usr set usr.logCount = :logCount , usr.isLocked = :isLocked , usr.lastLogin = :lastLogin where usr.userId=:userId";
Query query=null;
query = sess.createNativeQuery(queryStr);
query.setParameter("logCount", user.getLogCount());
query.setParameter("isLocked", user.getIsLocked());
query.setParameter("lastLogin", user.getLastLogin());
query.setParameter("userId", user.getUserId());
query.executeUpdate();
}
This is my code. This does not update mu user table in database , neither does this throw any error. It reflects the correct value till set parameter but after executeUpdate, I cannot see any update in my table. It would be really nice if anyone of you can tell me, where am I going wrong. Thanks in advance!
According to the hibernate documentation flush type MANUAL assume:
The Session flushing is delegated to the application, which must call Session.flush() explicitly in order to apply the persistence context changes.
So, you should explicitly call Session.flush() in the end of your method.
Also your updateUserState method should be ran inside a transaction:
Session sess = getSession();
sess.setFlushMode(FlushMode.MANUAL);
Transaction txn = sess.beginTransaction();
// ...
query.executeUpdate();
sess.flush();
txn.commit();
session.close();

Concurrent Read/Write MySQL EF Core

Using EF Core 2.2.6 and Pomelo.EntityFrameworkCore.MySql 2.2.6 (with MySqlConnector 0.59.2)). I have a model for UserData:
public class UserData
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public ulong ID { get; private set; }
[Required]
public Dictionary<string, InventoryItem> Inventory { get; set; }
public UserData()
{
Data = new Dictionary<string, string>();
}
}
I have a REST method that can be called that will add items to the user inventory:
using (var transaction = context.Database.BeginTransaction())
{
UserData data = await context.UserData.FindAsync(userId);
// there is code here to detect duplicate entries/etc, but I've removed it for brevity
foreach (var item in items) data.Inventory.Add(item.ItemId, item);
context.UserData.Update(data);
await context.SaveChangesAsync();
transaction.Commit();
}
If two or more calls to this method are made with the same user id then I get concurrent accesses (despite the transaction). This causes the data to sometimes be incorrect. For example, if the inventory is empty and then two calls are made to add items simultaneously (item A and item B), sometimes the database will only contain either A or B, and not both. From logging it appears that it is possible for EF to read from the database while the other read/write is still occurring, causing the code to have the incorrect state of the inventory for when it tries to write back to the db. So I tried marking the isolation level as serializable.
using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
Now I sometimes see an exception:
MySql.Data.MySqlClient.MySqlException (0x80004005): Deadlock found when trying to get lock; try restarting transaction
I don't understand how this code could deadlock... Anyways, I tried to proceed by wrapping this whole thing in a try/catch, and retry:
public static async Task<ResponseError> AddUserItem(Controller controller, MyContext context, ulong userId, List<InventoryItem> items, int retry = 5)
{
ResponseError result = null;
try
{
using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
UserData data = await context.UserData.FindAsync(userId);
// there is code here to detect duplicate entries/etc, but I've removed it for brevity
foreach (var item in items) data.Inventory.Add(item.ItemId, item);
context.UserData.Update(data);
await context.SaveChangesAsync();
transaction.Commit();
}
}
catch (Exception e)
{
if (retry > 0)
{
await Task.Delay(SafeRandomGenerator(10, 500));
return await AddUserItem(controller, context, userId, items, retry--);
}
else
{
// store exception and return error
}
}
return result;
}
And now I am back to the data being sometimes correct, sometimes not. So I think the deadlock is another problem, but this is the only method accessing this data. So, I'm at a loss. Is there a simple way to read from the database (locking the row in the process) and then writing back (releasing the lock on write) using EF Core? I've looked at using concurrency tokens, but this seems overkill for what appears (on the surface to me) to be a trivial task.
I added logging for mysql connector as well as asp.net server and can see the following failure:
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
=> RequestId:0HLUD39EILP3R:00000001 RequestPath:/client/AddUserItem => Server.Controllers.ClientController.AddUserItem (ServerSoftware)
Failed executing DbCommand (78ms) [Parameters=[#p1='?' (DbType = UInt64), #p0='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
UPDATE `UserData` SET `Inventory` = #p0
WHERE `ID` = #p1;
SELECT ROW_COUNT();
A total hack is to just delay the arrival of the queries by a bit. This works because the client is most likely to generate these calls on load. Normally back-to-back calls aren't expected, so spreading them out in time by delaying on arrival works. However, I'd rather find a correct approach, since this just makes it less likely to be an issue:
ResponseError result = null;
await Task.Delay(SafeRandomGenerator(100, 500));
using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
// etc
This isn't a good answer, because it isn't what I wanted to do, but I'll post it here as it did solve my problem. My problem was that I was trying to read the database row, modify it in asp.net, and then write it back, all within a single transaction and while avoiding deadlocks. The backing field is JSON type, and MySQL provides some JSON functions to help modify that JSON directly in the database. This required me to write SQL statements directly instead of using EF, but it did work.
The first trick was to ensure I could create the row if it didn't exist, without requiring a transaction and lock.
INSERT INTO UserData VALUES ({0},'{{}}','{{}}') ON DUPLICATE KEY UPDATE ID = {0};
I used JSON_REMOVE to delete keys from the JSON field:
UPDATE UserData as S set S.Inventory = JSON_REMOVE(S.Inventory,{1}) WHERE S.ID = {0};
and JSON_SET to add/modify entries:
UPDATE UserData as S set S.Inventory = JSON_SET(S.Inventory,{1},CAST({2} as JSON)) WHERE S.ID = {0};
Note, if you're using EF Core and want to call this using FromSql then you need to return the entity as part of your SQL statement. So you'll need to add something like this to each SQL statement:
SELECT * from UserData where ID = {0} LIMIT 1;
Here is a full working example as an extension method:
public static async Task<UserData> FindOrCreateAsync(this IQueryable<UserData> table, ulong userId)
{
string sql = "INSERT INTO UserData VALUES ({0},'{{}}','{{}}') ON DUPLICATE KEY UPDATE ID = {0}; SELECT * FROM UserData WHERE ID={0} LIMIT 1;";
return await table.FromSql(sql, userId).SingleOrDefaultAsync();
}
public static async Task<UserData> JsonRemoveInventory(this DbSet<UserData> table, ulong userId, string key)
{
if (!key.StartsWith("$.")) key = $"$.\"{key}\"";
string sql = "UPDATE UserData as S set S.Inventory = JSON_REMOVE(S.Inventory,{1}) WHERE S.ID = {0}; SELECT * from UserData where ID = {0} LIMIT 1;";
return await table.AsNoTracking().FromSql(sql, userId, key).SingleOrDefaultAsync();
}
Usage:
var data = await context.UserData.FindOrCreateAsync(userId);
await context.UserData.JsonRemoveInventory(userId, itemId);

How to Bulk update in HIbernate

I need to update multiple rows in my MySQL database using Hibernate. I have done this using JDBC where we have the support of batched Query. I want something like this in hibernate.
Does hibernate support batched Query?
Batched Query Example in jdbc:
// Create statement object
Statement stmt = conn.createStatement();
String SQL = "INSERT INTO Employees (id, first, last, age) " +
"VALUES(200,'Zia', 'Ali', 30)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create one more SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) " +
"VALUES(201,'Raj', 'Kumar', 35)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
int[] count = stmt.executeBatch();
Now when we issue stmt.executeBatch call Both Sql Query will be executed in a single jdbc round trip.
You may check the Hibernate documentation. Hibernate has some configuration properties that control (or disable) the use of JDBC batching.
If you issue the same INSERT multiple times and your entity does not use an identity generator, Hibernate will use JDBC batching transparently.
The configuration must enable the use of JDBC batching. Batching is disabled by default.
Configuring the Hibernate
The hibernate.jdbc.batch_size property defines the number of statements that Hibernate will batch before asking the driver to execute the batch. Zero or a negative number will disable the batching.
You can define a global configuration, e.g. in the persistence.xml, or define a session-specific configuration. To configure the session, you can use code like the following
entityManager
.unwrap( Session.class )
.setJdbcBatchSize( 10 );
Using the JDBC batching
As mentioned before, Hibernate call the JDBC batching transparently. If you wanna control the batching, you can use the flush() and clear() methods in the session.
The following is an example from the Documentation. It calls flush() and clear() when the number of insertions reach a batchSize value. It works efficiently if batchSize is lesser or equal than the configured hibernate.jdbc.batch_size.
EntityManager entityManager = null;
EntityTransaction txn = null;
try {
entityManager = entityManagerFactory().createEntityManager();
txn = entityManager.getTransaction();
txn.begin();
// define a batch size lesser or equal than the JDBC batching size
int batchSize = 25;
for ( int i = 0; i < entityCount; ++i ) {
Person Person = new Person( String.format( "Person %d", i ) );
entityManager.persist( Person );
if ( i > 0 && i % batchSize == 0 ) {
//flush a batch of inserts and release memory
entityManager.flush();
entityManager.clear();
}
}
txn.commit();
} catch (RuntimeException e) {
if ( txn != null && txn.isActive()) txn.rollback();
throw e;
} finally {
if (entityManager != null) {
entityManager.close();
}
}

Does MySQL has concurrent control on generating auto-increment value?

My colleague provides me a code segement that simulates Oracle's sequence:
// generate ticket
pstmt = conn.prepareStatement( "insert seq_pkgid values (NULL);" );
if(pstmt.executeUpdate() > 1) {
success = 1;
} else {
throw new Exception("Generating seq_pkgid sequence failed!");
}
pstmt.close();
pstmt = null;
// get ticket
pstmt = conn.prepareStatement( "select last_insert_id() as maxid" );
rs = pstmt.executeQuery();
if( rs.next() ) {
nSeq = rs.getInt( "maxid" );
}
rs.close();
rs = null;
pstmt.close();
pstmt = null;
But I wonder what if this code segment executed from 2 instances about the same time. Will they get same generated auto-increment value? Does MySQL has concurrent control, e.g. critical section or semaphore, when generating a new auto-increment value?
Yes ! If the column has AUTO_INCREMENT in column definition, MySQL will have an Auto Increment Lock on the column. Please refer
https://dev.mysql.com/doc/refman/5.7/en/innodb-auto-increment-handling.html
If you really need to generate a counter from DB, you can read here how to do on MySQL using InnodDB and Locking Reads.
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
Buy I'm wondering if you really need an autoincrement field or just a uniq identifier for the object you want to store; in this case maybe a UUID() is just fine:
https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_uuid

ChangeConflictException in Linq to Sql update

I'm in a world of pain with this one, and I'd very much appreciate it if someone could help out.
I have a DataContext attached to a single test table on a database. The test table is as follows:
CREATE TABLE [dbo].[LinqTests](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[StringValue] [varchar](255) NOT NULL,
[DateTimeValue] [datetime] NOT NULL,
[BooleanValue] [bit] NOT NULL,
CONSTRAINT [PK_LinqTests] PRIMARY KEY CLUSTERED ([ID] ASC))
ON [PRIMARY]
Using Linq, I can add, retrieve and delete rows from the test table, but I cannot update a row -- for an UPDATE, I always get a ChangeConflictException with an empty ObjectChangeConflict.MemberConflicts collection. Here is the code used:
var dataContext = new UniversityDataContext();
dataContext.Log = Console.Out;
for (int i = 1; i <= 1; i++) {
var linqTest = dataContext.LinqTests.Where(l => (l.ID == i)).FirstOrDefault();
if (null != linqTest) {
linqTest.StringValue += " I've been updated.";
}
else {
linqTest = new LinqTest {
BooleanValue = false,
DateTimeValue = DateTime.UtcNow,
StringValue = "I am in loop " + i + "."
};
dataContext.LinqTests.InsertOnSubmit(linqTest);
}
}
try {
dataContext.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException exception) {
Console.WriteLine("Optimistic concurrency error.");
Console.WriteLine(exception.Message);
Console.ReadLine();
}
Console.ReadLine();
Here is the log output for an update performed through the DataContext.
UPDATE [dbo].[LinqTests]
SET [StringValue] = #p3
WHERE ([ID] = #p0) AND ([StringValue] = #p1) AND ([DateTimeValue] = #p2) AND (NOT ([BooleanValue] = 1))
-- #p0: Input BigInt (Size = 0; Prec = 0; Scale = 0) [1]
-- #p1: Input VarChar (Size = 15; Prec = 0; Scale = 0) [I am in loop 1.]
-- #p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [3/19/2009 7:54:26 PM]
-- #p3: Input VarChar (Size = 34; Prec = 0; Scale = 0) [I am in loop 1. I've been updated.]
-- Context: SqlProvider(Sql2000) Model: AttributedMetaModel Build: 3.5.30729.1
I'm running this query on a clustered SQL Server 2000 (8.0.2039). I cannot, for the life of me, figure out what's going on here. Running a similar UPDATE query against the DB seems to work fine.
Thanks in advance for any help.
I finally figured out what was happening with this. Apparently, the "no count" option was turned on for this server.
In Microsoft SQL Server Management Studio 2005:
Right click on the server and click Properties
On the left hand of the Server Properties window, select the Connections page
Under Default connection options, ensure that "no count" is not selected.
Apparently, LINQ to SQL uses ##ROWCOUNT after updates to issue an automated optimistic concurrency check. Of course, if "no count" is turned on for the entire server, ##ROWCOUNT always returns zero, and LINQ to SQL throws a ConcurrencyException after issuing updates to the database.
This isn't the only update behavior LINQ to SQL uses. LINQ to SQL doesn't perform an automated optimistic concurrency check with ##ROWCOUNT if you have a TIMESTAMP column on your table.
Is it possible that any of the data for the row has changed between when it was retrieved and the update was attempted? Because LINQ->SQL has automatic concurrency checking that will validate the contents of the object against the currently stored values (like you see in the generated query). If it is possible that any of the fields have changed for the row in the DB vs the object LINQ is tracking then the update will fail. If this is occurring and for good reason and you know what fields, you can update the object in the DBML designer; select the field at cause and change the "Update Check" property to "Never".
I had the same issue with SQL Server 2008 and the connection option no count already turned of.
Instead of changing the Update Check property to Never (as Quintin suggests), I set it to WhenChanged and the issue was solved.
First log details about the problem, what row and what field is in conflict and what values are in conflict.
To implement such detail log, see my solution here:
What can I do to resolve a "Row not found or changed" Exception in LINQ to SQL on a SQL Server Compact Edition Database?