سؤال

تشغيل موقع Rails الآن باستخدام SQLite3.

مرة واحدة تقريبًا كل 500 طلب أو نحو ذلك، أحصل على

ActiveRecord::StatementInvalid (SQLite3::BusyException:قاعدة البيانات مقفلة:...

ما هي طريقة إصلاح هذا الأمر الذي سيكون أقل تدخلاً في الكود الخاص بي؟

أنا أستخدم SQLLite في الوقت الحالي لأنه يمكنك تخزين قاعدة البيانات في التحكم بالمصادر مما يجعل النسخ الاحتياطي أمرًا طبيعيًا ويمكنك دفع التغييرات بسرعة كبيرة.ومع ذلك، من الواضح أنه لم يتم إعداده للوصول المتزامن.سأنتقل إلى MySQL صباح الغد.

هل كانت مفيدة؟

المحلول

افتراضيًا، يعود sqlite على الفور مع وجود خطأ محظور ومشغول إذا كانت قاعدة البيانات مشغولة ومقفلة.يمكنك أن تطلب منه الانتظار والاستمرار في المحاولة لفترة قبل الاستسلام.يؤدي هذا عادةً إلى حل المشكلة، إلا إذا كان لديك آلاف المواضيع التي يمكنها الوصول إلى قاعدة البيانات الخاصة بك، عندما أوافق على أن sqlite سيكون غير مناسب.

    // set SQLite to wait and retry for up to 100ms if database locked
    sqlite3_busy_timeout( db, 100 );

نصائح أخرى

لقد ذكرت أن هذا موقع Rails.يسمح لك Rails بتعيين مهلة إعادة محاولة SQLite في ملف التكوين الخاص بقاعدة البيانات.yml:

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

يتم تحديد قيمة المهلة بالميلي ثانية.ومن المفترض أن تؤدي زيادته إلى 10 أو 15 ثانية إلى تقليل عدد BusyExceptions الذي تراه في السجل الخاص بك.

لكن هذا مجرد حل مؤقت.إذا كان موقعك يحتاج إلى التزامن الحقيقي، فسيتعين عليك الانتقال إلى محرك ديسيبل آخر.

كل هذه الأمور صحيحة، لكنها لا تجيب على السؤال الأرجح:لماذا يقوم تطبيق Rails الخاص بي أحيانًا برفع SQLite3::BusyException في الإنتاج؟

@الشلماني:ما هي بيئة استضافة الإنتاج؟هل هو على مضيف مشترك؟هل الدليل الذي يحتوي على قاعدة بيانات sqlite موجود في مشاركة NFS؟(على الأرجح، على مضيف مشترك).

من المحتمل أن تكون هذه المشكلة مرتبطة بظاهرة قفل الملفات باستخدام مشاركات NFS وافتقار SQLite إلى التزامن.

فقط للتسجيل.في أحد التطبيقات باستخدام Rails 2.3.8، اكتشفنا أن Rails كان يتجاهل خيار "المهلة" الذي اقترحه Rifkin Habsburg.

بعد إجراء المزيد من التحقيقات، وجدنا خطأً محتملاً ذا صلة في 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

و هذا كل شيء!لم نلاحظ انخفاضًا في الأداء والآن يدعم التطبيق العديد من الالتماسات دون انقطاع (ينتظر انتهاء المهلة).سكليتي هو لطيف!

bundle exec rake db:reset

لقد نجح الأمر بالنسبة لي، حيث سيتم إعادة تعيين وإظهار الترحيل المعلق.

مصدر: هذا الرابط

- 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(...)

يمكن أن يسمح Sqlite للعمليات الأخرى بالانتظار حتى تنتهي العملية الحالية.

أستخدم هذا الخط للاتصال عندما أعلم أنه قد يكون لدي عمليات متعددة تحاول الوصول إلى قاعدة بيانات Sqlite:

كون = sqlite3.connect('اسم الملف', مستوى العزل = "حصريًا")

وفقًا لوثائق Python Sqlite:

يمكنك التحكم في أي نوع من عبارات البداية التي يتم تنفيذها ضمنيًا (أو لا شيء على الإطلاق) عبر المعلمة العازلة_ل

واجهت مشكلة مماثلة مع rake db:migrate.كانت المشكلة أن دليل العمل كان على مشاركة SMB.لقد قمت بإصلاحه عن طريق نسخ المجلد إلى جهازي المحلي.

إذا كان لديك هذه المشكلة ولكن زيادة المهلة لا يغير شيئا, ، قد تكون لديك مشكلة أخرى تتعلق بالتزامن في المعاملات، إليك ملخصها:

  1. ابدأ المعاملة (يحصل على أ مشترك قفل)
  2. اقرأ بعض البيانات من قاعدة البيانات (ما زلنا نستخدم مشترك قفل)
  3. وفي الوقت نفسه، تبدأ عملية أخرى معاملة وكتابة البيانات (الحصول على محجوز قفل).
  4. ثم تحاول الكتابة، فأنت الآن تحاول طلب محجوز قفل
  5. يثير SQLite استثناء SQLITE_BUSY في الحال (بشكل مستقل عن المهلة الخاصة بك) لأن قراءاتك السابقة قد لا تكون دقيقة بحلول الوقت الذي يمكنها فيه الحصول على محجوز قفل.

إحدى الطرق لإصلاح ذلك هي تصحيح 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

معظم الإجابات مخصصة لـ Rails بدلاً من الياقوت الخام، وسؤال OPs هو لـ Rails، وهو أمر جيد.:)

لذلك أريد فقط أن أترك هذا الحل هنا إذا واجه أي مستخدم روبي خام هذه المشكلة، ولا يستخدم تكوين 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.

ما الجدول الذي يتم الوصول إليه عند مواجهة القفل؟

هل لديك معاملات طويلة الأمد؟

هل يمكنك معرفة الطلبات التي كانت لا تزال قيد المعالجة عند مواجهة القفل؟

Argh - لعنة وجودي خلال الأسبوع الماضي.يقوم Sqlite3 بتأمين ملف db عند أي عملية يكتب إلى قاعدة البيانات.أي أي استعلام من نوع UPDATE/INSERT (حدد أيضًا العد (*) لسبب ما).ومع ذلك، فإنه يتعامل مع القراءات المتعددة بشكل جيد.

لذا، أخيرًا شعرت بالإحباط بما يكفي لكتابة رمز قفل الخيط الخاص بي حول مكالمات قاعدة البيانات.من خلال التأكد من أن التطبيق يمكن أن يحتوي على موضوع واحد فقط للكتابة إلى قاعدة البيانات في أي وقت، تمكنت من التوسع إلى آلاف المواضيع.

ونعم، انها بطيئة مثل الجحيم.ولكنها أيضًا سريعة بما فيه الكفاية و صحيح, ، وهي خاصية جميلة.

لقد وجدت طريقًا مسدودًا في ملحق روبي sqlite3 وقم بإصلاحه هنا:جربها ومعرفة ما إذا كان هذا سيؤدي إلى حل مشكلتك.

    https://github.com/dxj19831029/sqlite3-ruby

فتحت طلب سحب ولم يعد هناك رد منهم.

على أي حال، من المتوقع حدوث بعض الاستثناءات المزدحمة كما هو موضح في sqlite3 نفسه.

كن حذرا مع هذا الشرط: sqlite مشغول

    The presence of a busy handler does not guarantee that it will be invoked when there is 
    lock contention. If SQLite determines that invoking the busy handler could result in a 
    deadlock, it will go ahead and return SQLITE_BUSY or SQLITE_IOERR_BLOCKED instead of 
    invoking the busy handler. Consider a scenario where one process is holding a read lock 
    that it is trying to promote to a reserved lock and a second process is holding a reserved 
    lock that it is trying to promote to an exclusive lock. The first process cannot proceed 
    because it is blocked by the second and the second process cannot proceed because it is 
    blocked by the first. If both processes invoke the busy handlers, neither will make any 
    progress. Therefore, SQLite returns SQLITE_BUSY for the first process, hoping that this 
    will induce the first process to release its read lock and allow the second process to 
    proceed.

إذا استوفيت هذا الشرط، فلن تعد المهلة صالحة بعد الآن.لتجنب ذلك، لا تضع التحديد داخل البدء/الالتزام.أو استخدم القفل الحصري للبدء/الالتزام.

أتمنى أن يساعدك هذا.:)

غالبًا ما يكون هذا خطأً متتابعًا لعمليات متعددة تصل إلى نفس قاعدة البيانات، على سبيل المثال.إذا لم يتم تعيين علامة "السماح بمثيل واحد فقط" في RubyMine

حاول تشغيل الأمر التالي فقد يفيدك:

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

من: روبي:SQLite3::BusyException:قاعدة البيانات مقفلة:

قد يؤدي هذا إلى مسح أي معاملة تعيق النظام

أعتقد أن هذا يحدث عندما تنتهي مهلة المعاملة.يجب عليك حقًا استخدام قاعدة بيانات "حقيقية".شيء مثل Drizzle أو MySQL.هل هناك أي سبب يجعلك تفضل SQLite على الخيارين السابقين؟

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top