I wrote the following transaction using spring data jpa.
#Override
#Transactional
public boolean removeAvailableStock(Long itemId, Long quantity) {
ItemEntity itemEntity = itemRepository.findById(itemId).orElseThrow(NoSuchItemException::new);
itemEntity.removeAvailableStock(quantity);
return true;
}
I tested the method in a multithreaded environment.
It caused a race condition.
So I set the transaction isolation level to Repeatable read level or Serializable level.
However, Race conditions continued to occur.
The only solution to this was to specify LockMode to findById Method in the repository.
Many documents say that transaction isolation level can solve the lost Update problem.
However, the transaction isolation level did not solve the concurrency problem.
Why doesn't the isolation level resolve lost updates in this situation?
What do many documents refer to as the level of isolation?
If the isolation level doesn't solve the concurrency problem, what's the purpose?
The test environment was h2 db
Related
I am using System.Data.SqlClient (4.6.1) in a dot net core 2.2 project. SqlClient maintains a pool of connections, and it has been reported that it leaks transaction isolation level if the same pooled connection is used for the next sql command.
For example, this is explained in this stackoverflow answer: https://stackoverflow.com/a/25606151/1250853
I tried looking for the right way to prevent this leak, but couldn't find a satisfactory solution.
I am thinking to follow this pattern:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
SET XACT_ABORT ON -- Turns on rollback if T-SQL statement raises a run-time error.
BEGIN TRANSACTION
SELECT * FROM MyTable;
-- removed complex statements for brevity. there are selects followed by insert.
COMMIT TRANSACTION
-- Set settings back to known defaults.
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SET XACT_ABORT OFF
Is this a good approach?
I would normally use two separate connection strings that are different (e.g. using tweaked Application Name values). Use one connection string for normal connections, the other for connections where you need serializable.
Since the connection strings are different, they go into separate pools. You may want to adjust other pool related settings if you think this will cause issues (e.g. limit the pool for the serializable to a much lower maximum if using it is rare and to prevent 2x default maximum connections from possibly being created).
I recommend never changing the transaction isolation level. If a transaction needs different locking behavior, use appropriate lock hints on selected queries.
The transaction isolation levels are blunt instruments, and often have surprising consequences.
SERIALIZABLE is especially problematic, as few people are prepared to handle the deadlocks it uses to enforce its isolation guarantees.
Also if you only change the transaction isolation level in stored procedure, SQL Server will automatically revert the session's isolation level after the procedure is complete.
Answering my own question based on #Zohar Peled's suggestion in the comments:
BEGIN TRY
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
SET XACT_ABORT ON -- Turns on rollback if T-SQL statement raises a run-time error.
BEGIN TRANSACTION
SELECT * FROM MyTable;
-- removed complex statements for brevity. there are selects followed by multiple inserts.
COMMIT TRANSACTION
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SET XACT_ABORT OFF
END TRY
BEGIN CATCH
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET XACT_ABORT OFF;
THROW;
END CATCH
EDIT:
If you are setting isloation level and xact_abort inside a stored proc, it's scoped to the stored proc only and you don't need to catch and turn everything off. https://learn.microsoft.com/en-us/sql/t-sql/statements/set-statements-transact-sql?view=sql-server-ver15 .
I have read committed snapshot isolation and allow isolation ON for my database. I'm still receiving a deadlock error. I'm pretty sure I know what is happening...
First transaction gets a sequence number at the beginning of its transaction.
Second one gets a later sequence number at the beginning of its transaction, but after the first transaction has already gotten its (second sequence number is more recent than first).
Second transaction makes it to the update statement first. When it checks the row versioning it sees the record that precedes both transactions since the first one hasn't reached the update yet. It finds that the row's sequence number is in a committed state and moves on it's merry way.
The first transaction takes it's turn and like the second transaction finds the same committed sequence number because it won't see the second one because it is newer than itself. When it tries to commit it finds that another transaction has already updated records that are trying to be committed and has to roll itself back.
Here is my question: Will this rollback appear as a deadlock in a trace?
In a comment attached to the original question you said: "I'm just wondering if an update conflict will appear as a deadlock or if it will appear as something different." I actually had exactly these types of concerns when I started looking into using snapshot isolation. Eventually I realized that there is significant difference between READ_COMMITTED_SNAPSHOT and isolation level SNAPSHOT.
The former uses row versioning for reads, but continues to use exclusive locking for writes. So, READ_COMMITTED_SNAPHOT is actually something in between pure pessimistic and pure optimistic concurrency control. Because it uses locks for writing, update conflicts are not possible, but deadlocks are. At least in SQL Server those deadlocks will be reported as deadlocks just as they are with 'normal' pessimistic locking.
The latter (isolation level SNAPSHOT) is pure optimistic concurrency control. Row versioning is used for both reads and writes. Deadlocks are not possible, but update conflicts are. The latter are reported as update conflicts and not as deadlocks.
The snapshot transaction is rolled back, and it receives the following error message:
Msg 3960, Level 16, State 4, Line 1
Snapshot isolation transaction aborted due to update conflict. You cannot use snapshot
isolation to access table 'Test.TestTran' directly or indirectly in database 'TestDatabase' to
update, delete, or insert the row that has been modified or deleted by another transaction.
Retry the transaction or change the isolation level for the update/delete statement.
To prevent deadlock enable both
ALLOW_SNAPSHOT_ISOLATION and READ_COMMITTED_SNAPSHOT
ALTER DATABASE [BD] SET READ_COMMITTED_SNAPSHOT ON;
ALTER DATABASE [BD] SET ALLOW_SNAPSHOT_ISOLATION ON;
here explain the differences
http://technet.microsoft.com/en-us/sqlserver/gg545007.aspx
I'm using Google App Engine with Java JPA.
The isolation level is Serializable inside transaction; Repeated Read outside transaction.
I search a lot of articles and everybody talks about behaviors between transactions, but no one mention about read within the same transaction.
Example :
/* data in User table with {ID=1,NAME='HANK'} */
BEGIN;
UPDATE User SET name = 'MING' WHERE ID=1;
SELECT name FROM User WHERE ID = 1;
COMMIT;
Result : Still {ID=1, NAME='HANK'}
My Questions:
Does Isolation level setting affect queries within the same transaction?
What is the rule with the same transaction?
Any queries done within the same transaction will be immediately visible to itself. In your example if you read row with ID of 1, you will see that it is updated. The difference is how other users are affected by your transaction. Depending on your isolation level the other user may:
Get blocked, the other user will wait until you commit / rollback
Read the data as it was before the transaction (snapshot isolation)
Read the data that is most up-to-date even without you committing (read uncommitted)
I'm just scratching the surface of isolation levels, there have been a lot of books written on the subject.
I am using transactionscope to ensure that data is being read to the database correctly. However, I may have a need to select some data (from another page) while the transaction is running. Would it be possible to do this? I'm very noob when it comes to databases.
I am using LinqToSQL and SQL Server 2005(dev)/2008(prod).
Yes, it is possible to still select data from a database while a transaction is running.
Data not affected by your transaction (for instance, rows in a table which are being not updated) can usually be read from other transactions. (In certain situations SQL Server will introduce a table lock that stops reads on all rows in the table but they are unusual and most often a symptom of something else going on in your query or on the server).
You need to look into Transaction Isolation Levels since these control exactly how this behaviour will work.
Here is the C# code to set the isolation level of a transaction scope.
TransactionOptions option = new TransactionOptions();
options.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
using (TransactionScope sc = new TransactionScope(TransactionScopeOption.Required, options)
{
// Code within transaction
}
In general, depending on the transaction isolation level specified on a transaction (or any table hints like NOLOCK) you get different levels of data locking that protect the rest of your application from activity tied up in your transaction. With a transaction isolation level of READUNCOMMITTED for example, you can see the writes within that transaction as they occur. This allows for dirty reads but also prevents (most) locks on data.
The other end of the scale is an isolation level like SERIALIZABLE which ensures that your transaction activity is entirely isolated until it has comitted.
In adition to the already provided advice, I would strongly recommend you look into snapshot isolation models. There is a good discussion at Using Snapshot Isolation. Enabling Read Committed Snapshot ON on the database can aleviate a lot of contention problems because readers are no longer blocked by writers. Since default reads are performed under read commited isolation mode, this simple database option switch has immediate benefits and requires no changes in the app.
There is no free lunch, so this comes at a price, in this case the price being aditional load on tempdb, see Row Versioning Resource Usage.
If howeever you are using explict isolation levels and specially if you use the default TransactionScope Serializable mode, then you'll have to review your code to enforce the more bening ReadCommited isolation level. If you don't know what isolation level you use, it means you use ReadCommited.
Yes, by default a TransactionScope will lock the tables involved in the transaction. If you need to read while a transaction is taking place, enter another TransactionScope with TransactionOptions IsolationLevel.ReadUncommitted:
TransactionScopeOptions = new TransactionScopeOptions();
options.IsolationLevel = IsolationLevel.ReadUncommitted;
using(var scope = new TransactionScope(
TransactionScopeOption.RequiresNew,
options
) {
// read the database
}
With a LINQ-to-SQL DataContext:
// db is DataContext
db.Transaction =
db.Connection.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);
Note that there is a difference between System.Transactions.IsolationLevel and System.Data.IsolationLevel. Yes, you read that correctly.
I have a bunch of utility procedures that just check for some conditions in the database and return a flag result. These procedures are run with READ UNCOMMITTED isolation level, equivalent to WITH NOLOCK.
I also have more complex procedures that are run with SERIALIZABLE isolation level. They also happen to have these same kind of checks in them.
So I decided to call these check procedures from within those complex procedures instead of replicating the check code.
Basically it looks like this:
CREATE PROCEDURE [dbo].[CheckSomething]
AS
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRANSACTION
-- Do checks
COMMIT TRANSACTION
and
CREATE PROCEDURE [dbo].[DoSomethingImportant]
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
EXECUTE [dbo].[CheckSomething]
-- Do some work
COMMIT TRANSACTION
Would it be okay to do that? Will the temporarily activated lower isolation level somehow break the higher level protection or is everything perfect safe?
EDIT: The execution goes smoothly without any errors.
It's all here for SQL Server 2005. A snippet:
When you change a transaction from one
isolation level to another, resources
that are read after the change are
protected according to the rules of
the new level. Resources that are read
before the change continue to be
protected according to the rules of
the previous level. For example, if a
transaction changed from READ
COMMITTED to SERIALIZABLE, the shared
locks acquired after the change are
now held until the end of the
transaction.
If you issue SET TRANSACTION ISOLATION
LEVEL in a stored procedure or
trigger, when the object returns
control the isolation level is reset
to the level in effect when the object
was invoked. For example, if you set
REPEATABLE READ in a batch, and the
batch then calls a stored procedure
that sets the isolation level to
SERIALIZABLE, the isolation level
setting reverts to REPEATABLE READ
when the stored procedure returns
control to the batch.
In this example:
Each isolation level is applied for the scope of the stored proc
Resources locked by DoSomethingImportant stay under SERIALIZABLE
Resources used by CheckSomething are READ UNCOMMITTED