العمل حول خطأ MySQL "تم العثور على Deadlock عند محاولة الحصول على القفل ؛ حاول إعادة تشغيل المعاملة "

StackOverflow https://stackoverflow.com/questions/2596005

سؤال

لديّ طاولة MySQL مع حوالي 5،000،000 صف يتم تحديثها باستمرار بطرق صغيرة من خلال عمليات Perl المتوازية التي يتم توصيلها عبر DBI. يحتوي الجدول على حوالي 10 أعمدة وعدة فهارس.

عملية واحدة شائعة إلى حد ما تؤدي إلى الخطأ التالي في بعض الأحيان:

DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction at Db.pm line 276.

بيان SQL الذي يثير الخطأ هو شيء من هذا القبيل:

UPDATE file_table SET a_lock = 'process-1234' WHERE param1 = 'X' AND param2 = 'Y' AND param3 = 'Z' LIMIT 47

يتم تشغيل الخطأ في بعض الأحيان فقط. سأقدر في 1 ٪ من المكالمات أو أقل. ومع ذلك ، لم يحدث أبدًا مع طاولة صغيرة وأصبح أكثر شيوعًا مع نمو قاعدة البيانات.

لاحظ أنني أستخدم حقل A_LOCK في File_table للتأكد من أن العمليات الأربع التي تقترب من الإقرار التي أقوم بتشغيلها لا تحاول العمل على نفس الصف. تم تصميم الحد لك لكسر عملهم إلى قطع صغيرة.

لم أفعل الكثير من الضبط على mysql أو dbd :: mysql. MySQL هو نشر Solaris قياسي ، ويتم إعداد اتصال قاعدة البيانات على النحو التالي:

my $dsn = "DBI:mysql:database=" . $DbConfig::database . ";host=${DbConfig::hostname};port=${DbConfig::port}";
my $dbh = DBI->connect($dsn, $DbConfig::username, $DbConfig::password, { RaiseError => 1, AutoCommit => 1 }) or die $DBI::errstr;

لقد رأيت على الإنترنت أن العديد من الأشخاص الآخرين قد أبلغوا عن أخطاء مماثلة وأن هذا قد يكون وضعًا حقيقيًا.

لدي سؤالان:

  1. ما هو بالضبط عن وضعي الذي يسبب الخطأ أعلاه؟

  2. هل هناك طريقة بسيطة للعمل من حولها أو تقليل ترددها؟ على سبيل المثال ، كيف يمكنني بالضبط "إعادة تشغيل المعاملة في سطر DB.PM 276"؟

شكرا لك مقدما.

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

المحلول

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

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

أنا أوصيك بقراءة هذه الصفحة في دليل MySQL. يحتوي على قائمة بالأشياء التي يجب القيام بها للمساعدة في التغلب على مسدود وتقليل تواترها.

نصائح أخرى

الإجابة صحيحة ، ومع ذلك فإن وثائق PERL حول كيفية التعامل مع الجمود متناثر بعض الشيء وربما مربكة مع Printerror و RiseerRor و Gealtherror. يبدو أنه بدلاً من الذهاب مع HandleRor ، استخدم على الطباعة ورفعه ثم استخدم شيئًا مثل المحاولة: Tiny لالتفاف الرمز والتحقق من وجود أخطاء. يعطي الرمز أدناه مثالًا حيث يكون رمز DB داخل حلقة من الوقت من شأنه إعادة تنفيذ عبارة SQL مخطئة كل 3 ثوانٍ. تحصل كتلة الصيد على $ _ وهي رسالة ERR المحددة. أقوم بتمرير هذا إلى دالة المعالج "dbi_err_handler" التي تتحقق $ _ مقابل مجموعة من الأخطاء والإرجاع 1 إذا كان يجب أن يستمر الكود (وبالتالي كسر الحلقة) أو 0 إذا كان مسدود ويجب إعادة تجهيزه ...

$sth = $dbh->prepare($strsql);
my $db_res=0;
while($db_res==0)
{
   $db_res=1;
   try{$sth->execute($param1,$param2);}
   catch
   {
       print "caught $_ in insertion to hd_item_upc for upc $upc\n";
       $db_res=dbi_err_handler($_); 
       if($db_res==0){sleep 3;}
   }
}

يجب أن يكون على الأقل على الأقل: dbi_err_handler ما يلي:

sub dbi_err_handler
{
    my($message) = @_;
    if($message=~ m/DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction/)
    {
       $caught=1;
       $retval=0; # we'll check this value and sleep/re-execute if necessary
    }
    return $retval;
}

يجب عليك تضمين أخطاء أخرى ترغب في التعامل معها وتعيين $ Retval اعتمادًا على ما إذا كنت ترغب في إعادة التنفيذ أو المتابعة ..

أمل أن هذا يساعد شخصاما -

لاحظ أنه إذا كنت تستخدم SELECT FOR UPDATE لإجراء فحص تفرد قبل الإدراج ، ستحصل على طريق مسدود لكل حالة سباق ما لم تمكن innodb_locks_unsafe_for_binlog اختيار. طريقة خالية من الجمود للتحقق من التفرد هي إدراج صف عمياء في جدول مع فهرس فريد باستخدام INSERT IGNORE, ، ثم للتحقق من عدد الصف المتأثر.

أضف السطر أدناه إلى my.cnf ملف

innodb_locks_unsafe_for_binlog = 1

#

1 - على
0 - قبالة

#

إن فكرة إعادة محاولة الاستعلام في حالة استثناء Deadlock جيد ، ولكن يمكن أن تكون بطيئة للغاية ، لأن MySQL Query سيستمر في انتظار إطلاق الأقفال. ولا تحاول MySQL أن تجد ما إذا كان هناك أي طريق مسدود ، وحتى بعد اكتشاف أن هناك طريقًا مسدودًا ، ينتظر بعض الوقت قبل طرد خيط من أجل الخروج من حالة Deadlock.

ما فعلته عندما واجهت هذا الموقف هو تنفيذ القفل في الكود الخاص بك ، لأنه آلية قفل MySQL تفشل بسبب الخلل. لذلك قمت بتنفيذ قفل مستوى الصف الخاص بي في رمز Java الخاص بي:

private HashMap<String, Object> rowIdToRowLockMap = new HashMap<String, Object>();
private final Object hashmapLock = new Object();
public void handleShortCode(Integer rowId)
{
    Object lock = null;
    synchronized(hashmapLock)
    {
      lock = rowIdToRowLockMap.get(rowId);
      if (lock == null)
      {
          rowIdToRowLockMap.put(rowId, lock = new Object());
      }
    }
    synchronized (lock)
    {
        // Execute your queries on row by row id
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top