I have some problem with this piece of code. When I run it, I want it to NOT lock the table(s) used by the transaction. To achieve this goal I set the isolation level to ReadUncommited.
The problem is that it still locks the table, it acts like if the isolationLevel were Serializable. I'm using SQL server 2008
Here is the code:
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
{
while (true)
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
connection.Open();
Console.WriteLine(Transaction.Current.IsolationLevel);
SqlUtils.ExecuteNonQuery(connection, "INSERT INTO test4 (test) VALUES ('ASDASDASD')");
}
Thread.Sleep(1000);
}
scope.Complete();
}
The transaction isolation level Read Uncommitted only applies when you read data (as the name says). It will read data that's not been committed yet.
There's no way I know of to stop SQL Server from putting locks on tables when you INSERT or UPDATE data.
ReadUncommitted, like the name suggests, impacts readers - i.e. should read operations take read-locks and key-range-locks; should they respect existing locks, etc.
I wonder whether IsolationLevel.Chaos would offer anything here, but please don't use that. Please.
If your competing reader needs to see uncommitted data, then change the isolation-level of the reader. Also, it goes without saying, but long-running transactions (and DTC/LTM transactions in particular) are not recommended.
Related
In order to prevent long read operations from blocking short, frequent write operations in my existing mixed OLTP/reporting web application backed by SQL Server 2016, I want to use Snapshot Isolation on a few long-running queries. The queries have already been well indexed and just take a long time to run due to large amounts of data, and while I may use RCSI later, I would like to start by using the less invasive Snapshot Isolation.
My question is: how do I enable Snapshot Isolation on my SELECT queries in C#? It seems I would have to wrap my select queries in a transaction, which just feels totally wrong.
List<Cat> cats = new List<Cat>();
TransactionOptions transactionOption = new TransactionOptions
{
IsolationLevel = IsolationLevel.Snapshot
};
using (TransactionScope transactionScope = new TransactionScope(TransactionScopeOption.Required, transactionOption))
{
using (SqlConnection sqlConnection = new SqlConnection(databaseConnectionString))
{
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
sqlCommand.CommandText = "proc_Select_A_Billion_Cats";
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlConnection.Open();
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
// SOME CODE HERE can read the Cat object in from data reader
// Add this cat to the collection
cats.Add(cat);
}
}
}
}
}
From http://www.levibotelho.com/development/plugging-isolation-leaks-in-sql-server/ I can see that in SQL Server 2014 and later the isolation level will be reset when the connection is returned to the pool, so that's good.
But can it be right to wrap a stored proc that does only SELECT in an ADO.NET transaction? Is there not some better way than this to implement Snapshot Isolation in C#?
No, you need to set the isolation level in the BeginTransaction methid:
If a database has been enabled for snapshot isolation but is not
configured for READ_COMMITTED_SNAPSHOT ON, you must initiate a
SqlTransaction using the IsolationLevel.Snapshot enumeration value
when calling the BeginTransaction method.
Otherwise, the default Read Committed mode is used and the READ will be blocked by the transactions modifying the table (possibly).
There is a performance and lock issue when using EF for a update-from-query case on MSSQL 2008. So I put ReadUncommitted transaction isolationlevel, hoping to resolve it, like this,
Before
using (MyEntities db = new MyEntities())
{
// large dataset
var data = from _Contact in db.Contact where _Contact.MemberId == 13 select _Contact;
for (var item in data)
item.Flag = 0;
// Probably db lock
db.SaveChanges();
}
After
using (var scope =
new TransactionScope(TransactionScopeOption.RequiresNew,
new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUncommitted }))
{
using (MyEntities db = new MyEntities())
{
// large dataset but with NOLOCK
var data = from _Contact in db.Contact where _Contact.MemberId == 13 select _Contact;
for (var item in data)
item.Flag = 0;
// Try avoid db lock
db.SaveChanges();
}
}
We use SQL profiler to trace. However, got these scripts in order,
(Expect read-uncommitted for the 1st script.)
Audit Login
set transaction isolation level read committed
SP:StmtStarting
SELECT
[Extent1].[ContactId] AS [ContactId],
[Extent1].[MemberId] AS [MemberId],
FROM [dbo].[Contact] AS [Extent1]
WHERE [Extent1].[MemberId] = #p__linq__0
Audit Login
set transaction isolation level read uncommitted
Though I could resend this request and make it right order (will show read-uncommitted for the following requests, same SPID), I wonder why it sent read-uncommitted command after read-committed command and how to fix by using EF and TransactionScope ? Thanks.
I think this is a red herring caused by relying on the Audit Login Event. This is not showing the moment when client tells server 'set transaction isolation level read uncommitted'. It is showing you what the isolation level is later on, when that connection is picked out of the pool and reused.
I verify this by adding Pooling=false to my connection string. Then, audit login always shows transaction isolation level read committed.
I have so far found no way, in SQL Profiler, of seeing the moment when EF sets the transaction level, nor any explicit begin tran.
I can kind of confirm that it is being set somewhere, by reading and logging the level:
const string selectIsolationLevel = #"SELECT CASE transaction_isolation_level WHEN 0 THEN 'Unspecified' WHEN 1 THEN 'ReadUncommitted' WHEN 2 THEN 'ReadCommitted' WHEN 3 THEN 'Repeatable' WHEN 4 THEN 'Serializable' WHEN 5 THEN 'Snapshot' END AS TRANSACTION_ISOLATION_LEVEL FROM sys.dm_exec_sessions where session_id = ##SPID";
static void ReadUncommitted()
{
using (var scope =
new TransactionScope(TransactionScopeOption.RequiresNew,
new TransactionOptions{ IsolationLevel = IsolationLevel.ReadUncommitted }))
using (myEntities db = new myEntities())
{
Console.WriteLine("Read is about to be performed with isolation level {0}",
db.Database.SqlQuery(typeof(string), selectIsolationLevel).Cast<string>().First()
);
var data = from _Contact in db.Contact where _Contact.MemberId == 13 select _Contact; // large results but with nolock
foreach (var item in data)
item.Flag = 0;
//Using Nuget package https://www.nuget.org/packages/Serilog.Sinks.Literate
//logger = new Serilog.LoggerConfiguration().WriteTo.LiterateConsole().CreateLogger();
//logger.Information("{#scope}", scope);
//logger.Information("{#scopeCurrentTransaction}", Transaction.Current);
//logger.Information("{#dbCurrentTransaction}", db.Database.CurrentTransaction);
//db.Database.ExecuteSqlCommand("-- about to save");
db.SaveChanges(); // Try avoid db lock
//db.Database.ExecuteSqlCommand("-- finished save");
//scope.Complete();
}
}
(I say ‘kind of’ because the statements each run in their own session)
Perhaps this is a long way of saying, yes EF transactions work correctly even if you can't prove it via Profiler.
According to the following note in the ADO.NET documentation Snapshot Isolation in SQL Server, the Isolation Level is not bound to the Transaction Scope as long as the underlying connection is pooled:
If a connection is pooled, resetting its isolation level does not
reset the isolation level at the server. As a result, subsequent
connections that use the same pooled inner connection start with their
isolation levels set to that of the pooled connection. An alternative
to turning off connection pooling is to set the isolation level
explicitly for each connection.
Thus I conclude that until SQL Server 2012, setting the isolation to any other level than ReadCommitted requires to either turn of connection pooling when creating the questionable SqlConnection or to set the Isolation Level in each connection explicitly to avoid unexpected behavior, including deadlocks. Alternatively the Connection Pool could be cleared by calling the ClearPool Method, but since this method is neither bound to the Transaction Scope nor the underlying connection, I don't think that it's approriate when several connections run simultaneously against the same pooled inner connection.
Referencing the post SQL Server 2014 reseting isolation level in the SQL forum and my own tests, such workarounds are obsolete when using SQL Server 2014 and a client driver with TDS 7.3 or higher.
I think a better solution is to perform update by generating a direct query (not selection and update entity by entity). In order to work with objects and not queries, you can use EntityFramework.Extended:
db.Contact.Update(C => c.MemberId == 13, c => new Contact { Flag = 0 });
This should generate something like UPDATE Contact SET Flag = 0 WHERE MemberId = 13 which is much more faster than your current solution.
If I remember correctly, this should generate its own transaction. If this must be executed in a transaction with other queries, `TransactionScope can still be used (you will have two transactions).
Also, isolation level can remain untouched (ReadCommitted).
[EDIT]
Chris's analysis shows exactly what happens. To make it even more relevant the following code shows the difference inside and outside of TransactionScope:
using (var db = new myEntities())
{
// this shows ReadCommitted
Console.WriteLine($"Isolation level outside TransactionScope = {db.Database.SqlQuery(typeof(string), selectIsolationLevel).Cast<string>().First()}");
}
using (var scope =
new TransactionScope(TransactionScopeOption.RequiresNew,
new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUncommitted }))
{
// this show ReadUncommitted
Console.WriteLine($"Isolation level inside TransactionScope = {db.Database.SqlQuery(typeof(string), selectIsolationLevel).Cast<string>().First()}");
using (myEntities db = new myEntities ())
{
var data = from _Contact in db.Contact where _Contact.MemberId == 13 select _Contact; // large results but with nolock
for (var item I data)
item.Flag = 0;
db.SaveChanges(); // Try avoid db lock
}
// this should be added to actually Commit the transaction. Otherwise it will be rolled back
scope.Complete();
}
Coming back to the actual problem (getting deadlocks), if we take a look of what Profiler is outputting during the whole thing, we see something like this (removed GOs):
BEGIN TRANSACTION
SELECT <all columns> FROM Contact
exec sp_reset_connection
exec sp_executesql N'UPDATE Contact
SET [Flag] = #0
WHERE ([Contact] = #1)
',N'#0 nvarchar(1000),#1 int',#0=N'1',#1=1
-- lots and lots of other UPDATEs like above
-- or ROLLBACK if scope.Complete(); is missed
COMMIT
This has two disadvantages:
Many round-trips - many queries are issued against the database, which puts more pressure on database engine and also takes much longer for the client
Long transaction - long transactions should be avoid as a tentative of minimizing deadlocks
So, the suggested solution should work better in your particular case (simple update).
In more complex cases, changing the isolation level might be needed.
I think that, if one deals with large processing of data (select millions, do something, update back etc.) a stored procedure might be the solution, since everything executes server-side.
I have some data that I want to insert in 2 databases(1 MSSQL Server, 2 POSTGRY). If record is inserted in both database than both should be Committed otherwise both should be rollback.
Can anyone help me that which IsolationLevel I have to set in my C# code ?
Currently I am using following code.
for SQL database,
var sqlTransactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.Serializable })
For PostGry database,
var npgsqlTransactionScope = npgsqlConnection.BeginTransaction()
But this code creates following issues sometimes:
- process still waiting for ShareLock on transaction
- canceling statement due to statement timeout
You can avoid/reduce locking of database/table using isolationlevel snapshot. See link here for details. Database should be configure for this isolation level.
if writing below codes: Error returns.i do like advise : http://stackoverflow.com/questions/794364/how-do-i-use-transactionscope-in-c
But only error change:The partner transaction manager has disabled its support for remote/network transactions Exception from HRESULT: 0x8004D025
i am using windows server 2003.
using (var stockMovementCtx = new StockMovementCtxDataContext())
{
using (var scope = new TransactionScope())
{
// do something....
}
scope.Complete();
}
but if i changed my codes ; every thing is ok:
using (var stockMovementCtx = new StockMovementCtxDataContext())
{
// do something....
}
How can i solve below error. This is really important.Please help me:((((
TransactionScope will elevate to DTC if necessary. It sounds like DTC is not correctly configured, usually due to firewall restrictions. Try a dtcping between the servers (important: in both directions).
DataContext by default wraps all operations within a Transaction, so you don't need to explicitly do Transaction while working with DataContext. Read this.
http://weblogs.asp.net/scottgu/archive/2007/07/11/linq-to-sql-part-4-updating-our-database.aspx
using (var stockMovementCtx = new StockMovementCtxDataContext())
{
// do something....
//everything until this point is within the transaction.
stockMovementCtx.SubmitChange();
}
Why we need TransactionScope ?
TransactionScope enables you to perform transactions beyond dataabse. Say you have series of operations as below and all are atomic and they need be performed within a transaction.
1.) Add record in Table 1
2.) Add record in Table 2
3.) Write a NEW file on the Disk
4.) Rename file X on the disk
5.) Update file record in Table 3
If you use SqlTransaction then only the operaration 1,3 and 5 can participate in the transaction but 3 and 4 cannot because they do not relate database at all. In such cases TrasanctionScope can help you. TransactionScope leverages the MSDTC (Distributed Trasanction co-coordinator) that can help you do transactions beyond database context. You can wrap all five operations within a TransactionScope transaction execute them atomically. It is also worth noting that TransactionScope supports nested transactions, that means one block of transaction can contain multiple set of transactions.
While using ADO.NET (maybe i am wrong, i dont know what its called) i notice that i can only begin a transaction with a connection and a command seems to have command.Transaction which gets me the transaction data but doesnt start a transaction itself? Actually while looking i see this in System.Data.SQLite
// Summary:
// The transaction associated with this command. SQLite only supports one transaction
// per connection, so this property forwards to the command's underlying connection.
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public SQLiteTransaction Transaction { get; set; }
So SQLite only supports one transaction period? i tried opening another connection but then my transaction threw an exception about the DB being locked. So i cannot have more then one connection concurrent as well?
One transaction per connection, yes, but it can have more than one connection (each with its own active transaction).
Update: interesting. I didn't know about shared-cache mode. If your connection is using that mode, only one transaction is available for all the connections using the same shared-cache. See SQLite shared-cache mode.
I'm not sure about multiple connections, it probably has to do with the fact that a connection locks the file since SQLite is a file-based DB and not a server-based DB (on a server-based DB the server keeps all of the files locked and deals with concurrent connections).
You can only have one transaction OPEN at a time. This should be make intuitive sense, since everything that happens after you begin a transaction is in that transaction, until either a rollback or commit. Then you can start a new one. SQLite requires all command to be in a transaction, so if you don't manually open a new one, it'll do so for you.
If you're worried about nested transactions, you can fake them with savepoint. Documentation
Within 1 transaction you can only read/write to 1 connection until the transaction is done. Therefore you have to pass the connection object if you do a business transaction that spans several sql statements like this:
public class TimeTableService
{
ITimeTableDataProvider _provider = new TimeTableDataProvider();
public void CreateLessonPlanner(WizardData wizardData)
{
using (var con = _provider.GetConnection())
using (var trans = new TransactionScope())
{
con.Open();
var weekListA = new List<Week>();
var weekListB = new List<Week>();
LessonPlannerCreator.CreateLessonPlanner(weekListA, weekListB, wizardData);
_provider.DeleteLessonPlanner(wizardData.StartDate, con);
_provider.CreateLessonPlanner(weekListA, con);
_provider.CreateLessonPlanner(weekListB, con);
_provider.DeleteTimeTable(TimeTable.WeekType.A, con);
_provider.StoreTimeTable(wizardData.LessonsWeekA.ToList<TimeTable>(), TimeTable.WeekType.A, con);
_provider.DeleteTimeTable(TimeTable.WeekType.B, con);
_provider.StoreTimeTable(wizardData.LessonsWeekB.ToList<TimeTable>(), TimeTable.WeekType.B, con);
trans.Complete();
}
}
}
The connection and transactoin resources are automatically released/closed by the using-statement.
In every dataprovider method you then do
using(var cmd = new SQLiteCommand("MyStatement",con)
{
// Create params + ExecuteNonQuery
}
The TransactionScope class is new in .NET 3.5 and is doing automatically a rollback if an exception occurs. Easy handling...