web-dev-qa-db-ja.com

SQLite3 :: BusyException

現在SQLite3を使用してRailsサイトを実行しています。

500回程度のリクエストに1回程度、

ActiveRecord :: StatementInvalid(SQLite3 :: BusyException:データベースがロックされています:.。

私のコードへの侵入を最小限に抑えるこれを修正する方法は何ですか?

現在SQLLiteを使用しているのは、DBをソース管理に保存できるため、バックアップが自然になり、変更をすばやくプッシュできるからです。ただし、実際には同時アクセス用に設定されていないことは明らかです。明日の朝、MySQLに移行します。

36
Shalmanese

デフォルトでは、データベースがビジーでロックされている場合、sqliteはブロックされたビジーエラーですぐに戻ります。あなたはそれを待って、あきらめる前にしばらく試してみるように頼むことができます。 sqliteが不適切であることに同意した場合、データベースにアクセスするスレッドが数千ある場合を除いて、これで通常問題が修正されます。

 //データベースがロックされている場合は最大100ms待機して再試行するようにSQLiteを設定します
 sqlite3_busy_timeout(db、100); 
9
ravenspoint

これはRailsサイトです。Railsを使用すると、database.yml構成ファイルでSQLiteの再試行タイムアウトを設定できます。

production:
  adapter: sqlite3
  database: db/mysite_prod.sqlite3
  timeout: 10000

タイムアウト値はミリ秒単位で指定されます。 10秒または15秒に増やすと、ログに表示されるBusyExceptionの数が減るはずです。

ただし、これは一時的な解決策にすぎません。サイトに真の同時実行性が必要な場合は、別のデータベースエンジンに移行する必要があります。

54
Rifkin Habsburg

これらのことはすべて真実ですが、それはおそらく質問に答えません:なぜ私のRailsアプリは本番環境でSQLite3 :: BusyExceptionを時々発生させるのですか?

@Shalmanese:本番ホスティング環境はどのようなものですか?共有ホスト上にありますか? NFS共有上のsqliteデータベースを含むディレクトリですか? (おそらく、共有ホスト上で)。

この問題は、NFS共有を使用したファイルロックの現象とSQLiteの同時実行性の欠如に関係している可能性があります。

3
ybakos

記録のためだけに。 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は素晴らしいです!

2
Ignacio Huerta
bundle exec rake db:reset

リセットされ、保留中の移行が表示されます。

Rake db:migrateでも同様の問題が発生しました。問題は、作業ディレクトリがSMB共有にあることでした。フォルダをローカルマシンにコピーして修正しました。

1
meredrica

Sqliteは、他のプロセスが現在のプロセスが終了するまで待機できるようにすることができます。

Sqlite DBにアクセスしようとしているプロセスが複数ある可能性があることがわかっている場合は、この行を使用して接続します。

conn = sqlite3.connect( 'filename'、isolation_level = 'exclusive'

Python Sqliteドキュメントによると:

Connect()呼び出しのisolation_levelパラメーターを介して、または接続のisolation_levelプロパティを介して、pysqliteが暗黙的に実行する(またはまったく実行しない)BEGINステートメントの種類を制御できます。

1
alfredodeza

ほとんどの回答は、生の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
Elindor

この問題が発生してもタイムアウトを増やしても何も変わらない場合は、トランザクションに別の同時実行の問題がある可能性があります。要約すると次のようになります。

  1. トランザクションを開始します([〜#〜]共有[〜#〜]ロックを取得します)
  2. DBからいくつかのデータを読み取ります(まだ[〜#〜]共有[〜#〜]ロックを使用しています)
  3. その間に、別のプロセスがトランザクションを開始し、データを書き込みます([〜#〜]予約済み[〜#〜]ロックを取得します)。
  4. 次に、書き込もうとすると、[〜#〜]予約済み[〜#〜]ロックを要求しようとしています。
  5. SQLiteはSQLITE_BUSY例外をすぐに(タイムアウトとは無関係に)発生させます。これは、以前の読み取りが[〜#〜]予約済み[〜#〜]ロック。

これを修正する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

詳細はこちら: https://Rails.lighthouseapp.com/projects/8994/tickets/5941-sqlite3busyexceptions-are-raised-immediately-in-some-cases-despite-setting-sqlite3_busy_timeout

1
Adrien Jarthon

ソース: このリンク

- 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(...)
0
Brian R. Bondy

ロックが発生したときにどのテーブルにアクセスしていますか?

長期にわたる取引はありますか?

ロックが発生したときに、どのリクエストがまだ処理されていたかを把握できますか?

0
David Medinets

次のコマンドを実行してみてください。役立つ場合があります。

ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;") 

From: Ruby:SQLite3 :: BusyException:データベースがロックされています:

これにより、システムを保持しているトランザクションがクリアされる可能性があります

0
John

ああ-先週の私の存在の悩みの種。 Sqlite3は、プロセスがデータベースに書き込みすると、dbファイルをロックします。 IE任意のUPDATE/INSERTタイプのクエリ(何らかの理由でcount(*)も選択します)。ただし、複数の読み取りを問題なく処理します。

それで、私はついにデータベース呼び出しの周りに独自のスレッドロックコードを書くのに十分な欲求不満になりました。アプリケーションがデータベースに書き込むスレッドを1つだけにすることができるようにすることで、数千のスレッドに拡張することができました。

そして、ええ、それは地獄のように遅いです。しかし、それはまた十分に速く、正しい、これは持っているべき素晴らしいプロパティです。

0
Voltaire

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を配置しないでください。または、開始/コミットに排他ロックを使用します。

お役に立てれば。 :)

0
xijing dai

これは多くの場合、同じデータベースにアクセスする複数のプロセスの連続した障害です。つまり、RubyMineで「1つのインスタンスのみを許可する」フラグが設定されていない場合です。

0
Anno2001