SQLite3::BusyException
-
09-06-2019 - |
سؤال
تشغيل موقع 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.لقد قمت بإصلاحه عن طريق نسخ المجلد إلى جهازي المحلي.
إذا كان لديك هذه المشكلة ولكن زيادة المهلة لا يغير شيئا, ، قد تكون لديك مشكلة أخرى تتعلق بالتزامن في المعاملات، إليك ملخصها:
- ابدأ المعاملة (يحصل على أ مشترك قفل)
- اقرأ بعض البيانات من قاعدة البيانات (ما زلنا نستخدم مشترك قفل)
- وفي الوقت نفسه، تبدأ عملية أخرى معاملة وكتابة البيانات (الحصول على محجوز قفل).
- ثم تحاول الكتابة، فأنت الآن تحاول طلب محجوز قفل
- يثير 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
معظم الإجابات مخصصة لـ 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 على الخيارين السابقين؟