単一スレッドでトランザクションを使用する場合の sqlite と SubSonic のロックの問題
-
21-09-2019 - |
質問
SubSonic および SQLite でトランザクションを使用しようとすると、ロック例外が発生します。私はこれを単一のスレッドから使用しており、データベースにアクセスする他のプロセスがないため、そのような問題が発生するとはまったく予想していませんでした。
以下のようなコードを書くと、ループ内の Save() の 2 回目の呼び出しで例外が発生します。つまり、全体での Save() の 3 回目の呼び出しで例外が発生します。
using (TransactionScope ts = new TransactionScope())
{
using (SharedDbConnectionScope sharedConnectinScope = new SharedDbConnectionScope())
{
SomeDALObject x = new SomeDALObject()
x.Property1 = "blah";
x.Property2 = "blah blah";
x.Save();
foreach (KeyValuePair<string, string> attribute in attributes)
{
AnotherDALObject y = new AnotherDALObject()
y.Property1 = attribute.Key
y.Property2 = attribute.Value
y.Save(); // this is where the exception is raised, on the 2nd time through this loop
}
}
}
上記のような using() ステートメントがある場合、または単に using (TransactionScope ts = new TransactionScope())
それから私は System.Data.SQLite.SQLiteException
メッセージ付き
データベースファイルがロックされています
データベースがロックされています
スタック トレースは次のとおりです。
at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt)
at System.Data.SQLite.SQLiteDataReader.NextResult()
at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave)
at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery()
at System.Data.SQLite.SQLiteTransaction..ctor(SQLiteConnection connection, Boolean deferredLock)
at System.Data.SQLite.SQLiteConnection.BeginDbTransaction(IsolationLevel isolationLevel)
at System.Data.SQLite.SQLiteConnection.BeginTransaction()
at System.Data.SQLite.SQLiteEnlistment..ctor(SQLiteConnection cnn, Transaction scope)
at System.Data.SQLite.SQLiteConnection.EnlistTransaction(Transaction transaction)
at System.Data.SQLite.SQLiteConnection.Open()
at SubSonic.SQLiteDataProvider.CreateConnection(String newConnectionString)
at SubSonic.SQLiteDataProvider.CreateConnection()
at SubSonic.SQLiteDataProvider.ExecuteScalar(QueryCommand qry)
at SubSonic.DataService.ExecuteScalar(QueryCommand cmd)
at SubSonic.ActiveRecord`1.Save(String userName)
at SubSonic.ActiveRecord`1.Save()
at (my line of code above).
using ステートメントを逆にネストし、SharedDbConnectionScope を外側にすると、次の結果が得られます。 TransactionException
「操作はトランザクションの状態に対して有効ではありません。」スタックトレースは次のとおりです。
at System.Transactions.TransactionState.EnlistVolatile(InternalTransaction tx, IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions, Transaction atomicTransaction)
at System.Transactions.Transaction.EnlistVolatile(IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)
at System.Data.SQLite.SQLiteEnlistment..ctor(SQLiteConnection cnn, Transaction scope)
at System.Data.SQLite.SQLiteConnection.EnlistTransaction(Transaction transaction)
at System.Data.SQLite.SQLiteConnection.Open()
at SubSonic.SQLiteDataProvider.CreateConnection(String newConnectionString)
at SubSonic.SQLiteDataProvider.CreateConnection()
at SubSonic.SQLiteDataProvider.ExecuteScalar(QueryCommand qry)
at SubSonic.DataService.ExecuteScalar(QueryCommand cmd)
at SubSonic.ActiveRecord`1.Save(String userName)
at SubSonic.ActiveRecord`1.Save()
at (my line of code above)
内部例外は「トランザクションタイムアウト」です
生成された DAL クラスにはカスタム コードはありません。また、これを引き起こす可能性があると考えられるその他の巧妙なコードもありません。
他にこのようなトランザクションの問題に遭遇した人はいますか、または問題をどこから探し始めるか教えてもらえますか?
ありがとう!
アップデート:バージョン 1.0.61 ~ 65 のリリース ノートにトランザクション関連の記述があることに気付きました (例: ここ) なので、最新バージョンの .Net Data Provider で動作するように SubSonic を更新すると、これらの問題の一部が解決される可能性があります...
解決
亜音速2.xのための改訂sqliteのプロバイダをやって、私は、既存の亜音速のSQLServerのテストに基づいてユニットテストのフルセットを作成しました。 (これらのテストはまた、改訂されたコードでチェックした。)失敗した唯一のテストは、トランザクション(あまりにも多分移行するもの)に関連するものでした。あなたが見てきたように、エラーメッセージ「データベースファイルがロックされています」。いくつかのものが動作しないので、サブソニックは、主にSQLiteのが行うよう、ファイルレベルのロックを行いませんSQL Serverのために書かれました。それはよりよいこれを処理するために書き換える必要があります。
あなたが持っているように、私はのTransactionScopeを使用したことがありません。私はこのように私の亜音速2.2取引を行わない、とSQLiteのプロバイダとのこれまでのところ何の問題。私はあなたが複数の行を扱う場合のSQLiteでトランザクションを使用する必要があるか、それは本当に遅いということを確認することができます。
public void DeleteStuff(List<Stuff> piaRemoves)
{
QueryCommandCollection qcc = new QueryCommandCollection();
foreach(Stuff item in piaRemoves)
{
Query qry1 = new Query(Stuff.Schema);
qry1.QueryType = QueryType.Delete;
qry1.AddWhere(Stuff.Columns.ItemID, item.ItemID);
qry1.AddWhere(Stuff.Columns.ColumnID, item.ColumnID);
qry1.AddWhere(Stuff.Columns.ParentID, item.ParentID);
QueryCommand cmd = qry1.BuildDeleteCommand();
qcc.Add(cmd);
}
DataService.ExecuteTransaction(qcc);
}
他のヒント
結局、Paul の提案を使用して、コードを次のように書き直しました。
QueryCommandCollection qcc = new QueryCommandCollection();
SomeDALObject x = new SomeDALObject()
x.Property1 = "blah";
x.Property2 = "blah blah";
qcc.Add(x.GetSaveCommand());
foreach (KeyValuePair<string, string> attribute in attributes)
{
AnotherDALObject y = new AnotherDALObject()
y.Property1 = attribute.Key
y.Property2 = attribute.Value
qcc.Add(y.GetSaveCommand());
}
DataService.ExecuteTransaction(qcc);
データベース ヒットに対するすべての準備がトランザクションが開かれる前に行われるため、トランザクションが開かれる時間が大幅に短縮されるため、これは実際にははるかに優れています。
子レコードに対して INSERT を実行するために自動生成された ID を取得する必要がある場合、これはあまりうまく機能しません。そのためには別のアプローチを使用する必要があります。
次に、他のスレッド/トランザクションの問題に遭遇しました。複数のスレッドで DataService.ExecuteTransaction() を同時に実行すると、AccessViolationExceptions と NullReferenceExceptions が発生し、基本的には少し混乱します。しかし、使用するために変更します ポールのフォーク SubSonic の SQLDataProvider が更新され、使用するように変更されました System.Data.SQLite v1.0.65.0 すぐに直ったようです。万歳!
アップデート:実際、SubSonic を sqlite で使用すると、依然としてスレッドの問題が発生します。基本的に、SubSonic の SQLiteDataProvider はマルチスレッドを処理するようには書かれていません。さらに今後も...
私たちは、テストにSQLのライトを使用しているデータベースのアクションに関連するコードです。 SQLのLiteは、ネストされたトランザクションをサポートしていません。 私たちは、NHibernateはと.NETトランザクションを持っていた同様の問題がありました。 最終的に我々はデータベース関連のコードをテストするためにSQL Expressを使用して落ち着いていた。