現在SQLite3を使用してRailsサイトを実行しています。
500回程度のリクエストに1回程度、
ActiveRecord :: StatementInvalid(SQLite3 :: BusyException:データベースがロックされています:.。
私のコードへの侵入を最小限に抑えるこれを修正する方法は何ですか?
現在SQLLiteを使用しているのは、DBをソース管理に保存できるため、バックアップが自然になり、変更をすばやくプッシュできるからです。ただし、実際には同時アクセス用に設定されていないことは明らかです。明日の朝、MySQLに移行します。
デフォルトでは、データベースがビジーでロックされている場合、sqliteはブロックされたビジーエラーですぐに戻ります。あなたはそれを待って、あきらめる前にしばらく試してみるように頼むことができます。 sqliteが不適切であることに同意した場合、データベースにアクセスするスレッドが数千ある場合を除いて、これで通常問題が修正されます。
//データベースがロックされている場合は最大100ms待機して再試行するようにSQLiteを設定します sqlite3_busy_timeout(db、100);
これはRailsサイトです。Railsを使用すると、database.yml構成ファイルでSQLiteの再試行タイムアウトを設定できます。
production:
adapter: sqlite3
database: db/mysite_prod.sqlite3
timeout: 10000
タイムアウト値はミリ秒単位で指定されます。 10秒または15秒に増やすと、ログに表示されるBusyExceptionの数が減るはずです。
ただし、これは一時的な解決策にすぎません。サイトに真の同時実行性が必要な場合は、別のデータベースエンジンに移行する必要があります。
これらのことはすべて真実ですが、それはおそらく質問に答えません:なぜ私のRailsアプリは本番環境でSQLite3 :: BusyExceptionを時々発生させるのですか?
@Shalmanese:本番ホスティング環境はどのようなものですか?共有ホスト上にありますか? NFS共有上のsqliteデータベースを含むディレクトリですか? (おそらく、共有ホスト上で)。
この問題は、NFS共有を使用したファイルロックの現象とSQLiteの同時実行性の欠如に関係している可能性があります。
記録のためだけに。 Rails 2.3.8のあるアプリケーションでは、RailsがRifkinHabsburgが提案した「タイムアウト」オプションを無視していることがわかりました。
さらに調査した結果、Rails dev: http://dev.rubyonrails.org/ticket/8811 )に関連する可能性のあるバグが見つかりました。さらに調査した結果、見つかった ソリューション (Rails 2.3.8)でテスト済み):
このActiveRecordファイルを編集します:activerecord-2.3.8/lib/active_record/connection_adapters/sqlite_adapter.rb
これを置き換えます:
def begin_db_transaction #:nodoc:
catch_schema_changes { @connection.transaction }
end
と
def begin_db_transaction #:nodoc:
catch_schema_changes { @connection.transaction(:immediate) }
end
そしてそれがすべてです!パフォーマンスの低下に気づかなかったため、アプリは中断することなく、さらに多くの請願をサポートします(タイムアウトを待ちます)。 Sqliteは素晴らしいです!
bundle exec rake db:reset
リセットされ、保留中の移行が表示されます。
Rake db:migrateでも同様の問題が発生しました。問題は、作業ディレクトリがSMB共有にあることでした。フォルダをローカルマシンにコピーして修正しました。
Sqliteは、他のプロセスが現在のプロセスが終了するまで待機できるようにすることができます。
Sqlite DBにアクセスしようとしているプロセスが複数ある可能性があることがわかっている場合は、この行を使用して接続します。
conn = sqlite3.connect( 'filename'、isolation_level = 'exclusive')
Python Sqliteドキュメントによると:
Connect()呼び出しのisolation_levelパラメーターを介して、または接続のisolation_levelプロパティを介して、pysqliteが暗黙的に実行する(またはまったく実行しない)BEGINステートメントの種類を制御できます。
ほとんどの回答は、生のRubyではなくRailsであり、OPの質問IS Railsの場合は問題ありません。:)
したがって、生のRubyユーザーがこの問題を抱えていて、yml構成を使用していない場合は、このソリューションをここに残しておきたいと思います。
接続をインスタンス化した後、次のように設定できます。
db = SQLite3::Database.new "#{path_to_your_db}/your_file.db"
db.busy_timeout=(15000) # in ms, meaning it will retry for 15 seconds before it raises an exception.
#This can be any number you want. Default value is 0.
この問題が発生してもタイムアウトを増やしても何も変わらない場合は、トランザクションに別の同時実行の問題がある可能性があります。要約すると次のようになります。
これを修正する1つの方法は、active_record
sqliteアダプターにパッチを適用して、トランザクションの開始時に[〜#〜]予約済み[〜#〜]ロックを直接取得することです。 :immediate
オプションをドライバーにパディングします。これによりパフォーマンスが少し低下しますが、少なくともすべてのトランザクションがタイムアウトを尊重し、次々に発生します。 prepend
(Ruby 2.0+)を使用してこれを行う方法は次のとおりです。これを初期化子に入れます。
module SqliteTransactionFix
def begin_db_transaction
log('begin immediate transaction', nil) { @connection.transaction(:immediate) }
end
end
module ActiveRecord
module ConnectionAdapters
class SQLiteAdapter < AbstractAdapter
prepend SqliteTransactionFix
end
end
end
ソース: このリンク
- Open the database
db = sqlite3.open("filename")
-- Ten attempts are made to proceed, if the database is locked
function my_busy_handler(attempts_made)
if attempts_made < 10 then
return true
else
return false
end
end
-- Set the new busy handler
db:set_busy_handler(my_busy_handler)
-- Use the database
db:exec(...)
ロックが発生したときにどのテーブルにアクセスしていますか?
長期にわたる取引はありますか?
ロックが発生したときに、どのリクエストがまだ処理されていたかを把握できますか?
次のコマンドを実行してみてください。役立つ場合があります。
ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;")
From: Ruby:SQLite3 :: BusyException:データベースがロックされています:
これにより、システムを保持しているトランザクションがクリアされる可能性があります
ああ-先週の私の存在の悩みの種。 Sqlite3は、プロセスがデータベースに書き込みすると、dbファイルをロックします。 IE任意のUPDATE/INSERTタイプのクエリ(何らかの理由でcount(*)も選択します)。ただし、複数の読み取りを問題なく処理します。
それで、私はついにデータベース呼び出しの周りに独自のスレッドロックコードを書くのに十分な欲求不満になりました。アプリケーションがデータベースに書き込むスレッドを1つだけにすることができるようにすることで、数千のスレッドに拡張することができました。
そして、ええ、それは地獄のように遅いです。しかし、それはまた十分に速く、正しい、これは持っているべき素晴らしいプロパティです。
Sqlite3 Ruby拡張機能でデッドロックを見つけ、ここで修正します。試してみて、これで問題が解決するかどうかを確認してください。
https://github.com/dxj19831029/sqlite3-Ruby
プルリクエストを開きましたが、応答がありません。
とにかく、sqlite3自体で説明されているように、いくつかのビジー例外が予想されます。
この条件に注意してください: sqlitebusy
ビジーハンドラーが存在しても、 ロックの競合が発生したときに呼び出されるとは限りません。ビジーハンドラーを呼び出すと デッドロックが発生する可能性があるとSQLiteが判断した場合、SQLiteは先に進み、ビジーハンドラーを呼び出す の代わりにSQLITE_BUSYまたはSQLITE_IOERR_BLOCKEDを返します。 1つのプロセスが予約済みロックに昇格しようとしている読み取りロック を保持していて、2番目のプロセスが排他的ロックに昇格しようとしている予約済み ロックを保持しているシナリオを考えてみます。 。最初のプロセスは2番目のプロセスによってブロックされているため を続行できず、2番目のプロセスは最初のプロセスによって ブロックされているため続行できません。両方のプロセスがビジーハンドラーを呼び出す場合、どちらも を進行させません。したがって、SQLiteは最初のプロセスに対してSQLITE_BUSYを返し、この が最初のプロセスに読み取りロックを解放し、2番目のプロセスが 続行できるようにすることを期待します。
この条件を満たすと、タイムアウトは無効になります。これを回避するには、begin/commit内にselectを配置しないでください。または、開始/コミットに排他ロックを使用します。
お役に立てれば。 :)
これは多くの場合、同じデータベースにアクセスする複数のプロセスの連続した障害です。つまり、RubyMineで「1つのインスタンスのみを許可する」フラグが設定されていない場合です。