it-swarm.asia

كيفية تصحيح قفل مهلة الانتظار تجاوز على الخلية؟

في سجلات أخطاء الإنتاج الخاصة بي ، أرى أحيانًا:

SQLSTATE [HY000]: خطأ عام: 1205 تجاوز مهلة انتظار التأمين ؛ حاول إعادة تشغيل المعاملة

أعرف أي استعلام يحاول الوصول إلى قاعدة البيانات في تلك اللحظة ، ولكن هل هناك طريقة لمعرفة أي استعلام لديه القفل في تلك اللحظة بالتحديد؟

238
Matt McCormick

ما يعطي هذا بعيدا هو كلمة الصفقة . يتضح من البيان أن الاستعلام كان يحاول تغيير صف واحد على الأقل في جدول InnoDB أو أكثر.

نظرًا لأنك تعرف الاستعلام ، فإن جميع الجداول التي يتم الوصول إليها تعتبر مرشحة لكونها الجاني.

من هناك ، يجب أن تكون قادرًا على تشغيل SHOW ENGINE INNODB STATUS\G

يجب أن تكون قادرًا على رؤية الجدول (الجداول) المتأثر

يمكنك الحصول على جميع أنواع معلومات التأمين والقفل الإضافية.

هذه عينة من أحد عملائي:

mysql> show engine innodb status\G
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
110514 19:44:14 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 4 seconds
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 9014315, signal count 7805377
Mutex spin waits 0, rounds 11487096053, OS waits 7756855
RW-shared spins 722142, OS waits 211221; RW-excl spins 787046, OS waits 39353
------------------------
LATEST FOREIGN KEY ERROR
------------------------
110507 21:41:35 Transaction:
TRANSACTION 0 606162814, ACTIVE 0 sec, process no 29956, OS thread id 1223895360 updating or deleting, thread declared inside InnoDB 499
mysql tables in use 1, locked 1
14 lock struct(s), heap size 3024, 8 row lock(s), undo log entries 1
MySQL thread id 3686635, query id 124164167 10.64.89.145 viget updating
DELETE FROM file WHERE file_id in ('6dbafa39-7f00-0001-51f2-412a450be5cc' )
Foreign key constraint fails for table `backoffice`.`attachment`:
,
  CONSTRAINT `attachment_ibfk_2` FOREIGN KEY (`file_id`) REFERENCES `file` (`file_id`)
Trying to delete or update in parent table, in index `PRIMARY` Tuple:
DATA Tuple: 17 fields;
 0: len 36; hex 36646261666133392d376630302d303030312d353166322d343132613435306265356363; asc 6dbafa39-7f00-0001-51f2-412a450be5cc;; 1: len 6; hex 000024214f7e; asc   $!O~;; 2: len 7; hex 000000400217bc; asc    @   ;; 3: len 2; hex 03e9; asc   ;; 4: len 2; hex 03e8; asc   ;; 5: len 36; hex 65666635323863622d376630302d303030312d336632662d353239626433653361333032; asc eff528cb-7f00-0001-3f2f-529bd3e3a302;; 6: len 40; hex 36646234376337652d376630302d303030312d353166322d3431326132346664656366352e6d7033; asc 6db47c7e-7f00-0001-51f2-412a24fdecf5.mp3;; 7: len 21; hex 416e67656c73204e6f7720436f6e666572656e6365; asc Angels Now Conference;; 8: len 34; hex 416e67656c73204e6f7720436f6e666572656e6365204a756c7920392c2032303131; asc Angels Now Conference July 9, 2011;; 9: len 1; hex 80; asc  ;; 10: len 8; hex 8000124a5262bdf4; asc    JRb  ;; 11: len 8; hex 8000124a57669dc3; asc    JWf  ;; 12: SQL NULL; 13: len 5; hex 8000012200; asc    " ;; 14: len 1; hex 80; asc  ;; 15: len 2; hex 83e8; asc   ;; 16: len 4; hex 8000000a; asc     ;;

But in child table `backoffice`.`attachment`, in index `PRIMARY`, there is a record:
PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 30; hex 36646261666133392d376630302d303030312d353166322d343132613435; asc 6dbafa39-7f00-0001-51f2-412a45;...(truncated); 1: len 30; hex 38666164663561652d376630302d303030312d326436612d636164326361; asc 8fadf5ae-7f00-0001-2d6a-cad2ca;...(truncated); 2: len 6; hex 00002297b3ff; asc   "   ;; 3: len 7; hex 80000040070110; asc    @   ;; 4: len 2; hex 0000; asc   ;; 5: len 30; hex 416e67656c73204e6f7720436f6e666572656e636520446f63756d656e74; asc Angels Now Conference Document;;

------------
TRANSACTIONS
------------
Trx id counter 0 620783814
Purge done for trx's n:o < 0 620783800 undo n:o < 0 0
History list length 35
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1192212800
MySQL thread id 5341758, query id 189708501 127.0.0.1 lwdba
show innodb status
---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1223895360
MySQL thread id 5341667, query id 189706152 10.64.89.145 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1227888960
MySQL thread id 5341556, query id 189699857 172.16.135.63 lwdba
---TRANSACTION 0 620781112, not started, process no 29956, OS thread id 1222297920
MySQL thread id 5341511, query id 189696265 10.64.89.143 viget
---TRANSACTION 0 620783736, not started, process no 29956, OS thread id 1229752640
MySQL thread id 5339005, query id 189707998 10.64.89.144 viget
---TRANSACTION 0 620783785, not started, process no 29956, OS thread id 1198602560
MySQL thread id 5337583, query id 189708349 10.64.89.145 viget
---TRANSACTION 0 620783469, not started, process no 29956, OS thread id 1224161600
MySQL thread id 5333500, query id 189708478 10.64.89.144 viget
---TRANSACTION 0 620781240, not started, process no 29956, OS thread id 1198336320
MySQL thread id 5324256, query id 189708493 10.64.89.145 viget
---TRANSACTION 0 617458223, not started, process no 29956, OS thread id 1195141440
MySQL thread id 736, query id 175038790 Has read all relay log; waiting for the slave I/O thread to update it
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (write thread)
Pending normal aio reads: 0, aio writes: 0,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
519878 OS file reads, 18962880 OS file writes, 13349046 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 6.25 writes/s, 4.50 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 1190, seg size 1192,
174800 inserts, 174800 merged recs, 54439 merges
Hash table size 35401603, node heap has 35160 buffer(s)
0.50 hash searches/s, 11.75 non-hash searches/s
---
LOG
---
Log sequence number 28 1235093534
Log flushed up to   28 1235093534
Last checkpoint at  28 1235091275
0 pending log writes, 0 pending chkp writes
12262564 log i/o's done, 3.25 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 18909316674; in additional pool allocated 1048576
Dictionary memory allocated 2019632
Buffer pool size   1048576
Free buffers       175763
Database pages     837653
Modified db pages  6
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages read 770138, created 108485, written 7795318
0.00 reads/s, 0.00 creates/s, 4.25 writes/s
Buffer pool hit rate 1000 / 1000
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
1 read views open inside InnoDB
Main thread process no. 29956, id 1185823040, state: sleeping
Number of rows inserted 6453767, updated 4602534, deleted 3638793, read 388349505551
0.25 inserts/s, 1.25 updates/s, 0.00 deletes/s, 2.75 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

1 row in set, 1 warning (0.00 sec)

يجب أن تفكر في زيادة قيمة مهلة انتظار القفل لـ InnoDB عن طريق تعيين innodb_lock_wait_timeout ، الافتراضي هو 50 ثانية

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set (0.01 sec)

يمكنك ضبطه على قيمة أعلى في /etc/my.cnf نهائيًا باستخدام هذا الخط

[mysqld]
innodb_lock_wait_timeout=120

وإعادة تشغيل الخلية. إذا لم تتمكن من إعادة تشغيل mysql في هذا الوقت ، فقم بتشغيل هذا:

SET GLOBAL innodb_lock_wait_timeout = 120; 

يمكنك أيضا تعيينه فقط لمدة جلستك

SET innodb_lock_wait_timeout = 120; 

تليها الاستعلام الخاص بك

236
RolandoMySQLDBA

كما ذكر شخص ما في واحدة من العديد من SO مؤشرات الترابط المتعلقة بهذه المشكلة: في بعض الأحيان تظهر العملية التي أغلقت الجدول كما النوم في قائمة العملية! كنت أمزق شعري حتى قتلت كل خيوط النوم التي كانت مفتوحة في قاعدة البيانات المعنية (لم يكن أي منها نشطًا في ذلك الوقت). هذا أخيرًا فتح الجدول واترك استعلام التحديث يعمل.

قال المعلق شيئًا مشابهًا لـ "في بعض الأحيان ، يقوم مؤشر ترابط MySQL بإغلاق جدول ، ثم ينام أثناء انتظار حدوث شيء غير متعلق بـ MySQL."

بعد إعادة مراجعة سجل show engine innodb status (بمجرد تتبع العميل المسؤول عن القفل) ، لاحظت أن الخيط المتوقف في السؤال مدرج في أسفل قائمة المعاملات ، أسفل الاستعلامات النشطة التي كانت على وشك خطأ خارج بسبب قفل المجمدة:

------------------
---TRANSACTION 2744943820, ACTIVE 1154 sec(!!)
2 lock struct(s), heap size 376, 2 row lock(s), undo log entries 1
MySQL thread id 276558, OS thread handle 0x7f93762e7710, query id 59264109 [ip] [database] cleaning up
Trx read view will not see trx with id >= 2744943821, sees < 2744943821

(لست متأكداً مما إذا كانت رسالة "عرض قراءة TRX" مرتبطة بالقفل المجمد ، ولكن على عكس المعاملات النشطة الأخرى ، لا تظهر هذه الرسالة مع الاستعلام الذي تم إصداره ، وبدلاً من ذلك تدعي أن المعاملة "تنظيف" ، إلا أنها تحتوي على عدة أقفال الصف)

تتمثل مغزى القصة في أن المعاملة يمكن أن تكون نشطة على الرغم من أن الخيط نائم.

71
Eric Lawler

بسبب شعبية MySQL ، فلا عجب تجاوز مهلة انتظار القفل ؛ حاول إعادة تشغيل المعاملة استثناء يحصل على الكثير من الاهتمام على SO.

كلما زاد التنافس لديك ، زادت فرصة الوصول إلى حالة توقف تام ، والتي سيتم حل محرك أقراص DB من خلال انقضاء مهلة واحدة من المعاملات التي وصلت إلى طريق مسدود. أيضًا ، المعاملات طويلة الأمد التي عدّلت (على سبيل المثال UPDATE أو DELETE) عددًا كبيرًا من الإدخالات (التي تأخذ أقفالًا لتجنب الشذوذات القذرة في الكتابة كما هو موضح في كتاب جافا عالي الأداء ) لتوليد الصراعات مع المعاملات الأخرى.

على الرغم من InnoDB MVCC ، لا يزال بإمكانك طلب أقفال صريحة باستخدام جملة FOR UPDATE . ومع ذلك ، على عكس قواعد البيانات الشائعة الأخرى (Oracle ، MSSQL ، PostgreSQL ، DB2) ، MySQL يستخدم REPEATABLE_READ كمستوى عزل افتراضي .

الآن ، يتم الاحتفاظ الأقفال التي حصلت عليها (إما عن طريق تعديل الصفوف أو باستخدام قفل صريح) ، لمدة المعاملة الجارية حاليا. إذا كنت تريد توضيحًا جيدًا للفرق بين REPEATABLE_READ و READ COMMITTED فيما يتعلق بالقفل ، فيرجى اقرأ مقالة Percona .

في تكرار يمكن قراءة كل قفل المكتسبة خلال معاملة لمدة المعاملة.

في قراءة الالتزام ، يتم إصدار الأقفال التي لا تتطابق مع الفحص بعد اكتمال البيان.

...

هذا يعني أنه في READ COMMITTED ، تكون المعاملات الأخرى مجانية لتحديث الصفوف التي لم تكن قادراً على تحديثها (في REPEATABLE READ) بمجرد اكتمال عبارة UPDATE.

هذا أكثر تقييدًا مستوى العزل (REPEATABLE_READ ، SERIALIZABLE) كلما زادت فرصة حدوث حالة توقف تام. هذه ليست مشكلة "بحد ذاتها" ، إنها مفاضلة.

يمكنك الحصول على نتائج جيدة للغاية مع READ_COMMITED ، حيث تحتاج منع التحديث المفقود على مستوى التطبيق عند استخدام المعاملات المنطقية التي تمتد عبر طلبات HTTP متعددة. يستهدف النهج { التأمين المتفائل النهج التحديثات المفقودة التي قد تحدث حتى لو كنت تستخدم SERIALIZABLE مستوى العزل مع تقليل تنافس القفل عن طريق السماح لك باستخدام READ_COMMITED.

34
Vlad Mihalcea

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

MySQL عادة ما تكون قادرة على التعامل مع حالة توقف تام إذا تم بناؤها "بشكل صحيح" في اثنين من المعاملات. MySQL ثم يقتل/يتراجع عن الصفقة الوحيدة التي تمتلك عددًا أقل من الأقفال (أقل أهمية لأنها ستؤثر على عدد أقل من الصفوف) وتتيح للطرف الآخر الانتهاء.

الآن ، لنفترض وجود عمليتين A و B و 3 معاملات:

Process A Transaction 1: Locks X
Process B Transaction 2: Locks Y
Process A Transaction 3: Needs Y => Waits for Y
Process B Transaction 2: Needs X => Waits for X
Process A Transaction 1: Waits for Transaction 3 to finish

(see the last two paragraph below to specify the terms in more detail)

=> deadlock 

هذا هو الإعداد المؤسف للغاية لأن MySQL لا يمكن أن نرى هناك حالة توقف تام (تمتد خلال 3 المعاملات). ما يفعله MySQL هو ... لا شيء! إنه ينتظر فقط ، لأنه لا يعرف ماذا يفعل. ينتظر حتى يتجاوز القفل الذي تم الحصول عليه الأول المهلة (العملية A المعاملة 1: Locks X) ، ثم سيؤدي ذلك إلى إلغاء قفل Lock X ، الذي يفتح المعاملة 2 إلخ.

الفن هو معرفة ما (أي استعلام) يؤدي القفل الأول (القفل X). ستتمكن بسهولة من رؤية (show engine innodb status) أن المعاملة 3 تنتظر المعاملة 2 ، لكنك لن ترى أي معاملة 2 تنتظر (المعاملة 1). لن يقوم MySQL بطباعة أي أقفال أو استعلام مرتبط بالمعاملة 1. سيكون التلميح الوحيد هو أنه في أسفل قائمة المعاملات (للطباعة show engine innodb status) ، سترى المعاملة 1 على ما يبدو لا تفعل شيئًا (ولكن في الواقع تنتظر المعاملة 3 لانهاء).

يتم وصف تقنية كيفية العثور على استعلام SQL الذي يتسبب في تأمين القفل (القفل X) لمعاملة معينة تنتظر هنا Tracking MySQL query history in long running transactions

إذا كنت تتساءل عن العملية والمعاملة بالضبط في المثال. العملية هي PHP عملية. المعاملة هي معاملة كما هو محدد بواسطة innodb-trx-table . في حالتي ، كان لديّ عمليتان [PHP) ، في كل مرة بدأت فيها معاملة يدوياً. كان الجزء المثير للاهتمام أنه على الرغم من أنني بدأت معاملة واحدة في عملية ما ، فإن MySQL تستخدم داخليًا في الواقع معاملتين منفصلتين (ليس لدي أدنى فكرة عن السبب ، ربما يمكن لبعض مطوري MySQL شرح ذلك).

تقوم MySQL بإدارة المعاملات الخاصة بها داخليًا وقررت (في حالتي) استخدام معاملتين للتعامل مع جميع طلبات SQL الواردة من PHP العملية (العملية A). العبارة التي تشير إلى أن المعاملة 1 تنتظر انتهاء المعاملة 3 هي شيء داخلي في MySQL. MySQL "يعرف" تم بالفعل إنشاء معاملة 1 والمعاملة 3 كجزء من طلب "معاملة" واحد (من العملية A). الآن تم حظر "المعاملة" بأكملها لأنه تم حظر المعاملة 3 (جزء فرعي من "المعاملة"). نظرًا لأن "المعاملة" لم تتمكن من إنهاء المعاملة 1 (وأيضًا كانت جزءًا من "المعاملة") تم وضع علامة على أنها لم تنته أيضًا. هذا ما قصدته "المعاملة 1 تنتظر إنهاء المعاملة 3".

18
Tomas Bilka

تكمن المشكلة الكبيرة في هذا الاستثناء في أنه غير قابل للتكرار عادة في بيئة اختبار ، ونحن لسنا على وشك تشغيل حالة محرك innodb عندما يحدث ذلك على المنتج. لذلك في أحد المشاريع ، وضعت الكود أدناه في كتلة catch لهذا الاستثناء. ساعدني ذلك في التعرف على حالة المحرك عند حدوث الاستثناء. هذا ساعد كثيرا.

Statement st = con.createStatement();
ResultSet rs =  st.executeQuery("SHOW ENGINE INNODB STATUS");
while(rs.next()){
    log.info(rs.getString(1));
    log.info(rs.getString(2));
    log.info(rs.getString(3));
}
12
Maruthi

ألقِ نظرة على صفحة man الخاصة بـ pt-deadlock-logger utility :

brew install percona-toolkit
pt-deadlock-logger --ask-pass server_name

يستخرج المعلومات من engine innodb status المذكورة أعلاه ، كما يمكن استخدامه لإنشاء daemon الذي يعمل كل 30 ثانية.

9
Andrei Sura

استقراءًا من إجابة رولاندو أعلاه ، فهذه هي التي تمنع استفسارك:

---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget

إذا كنت بحاجة إلى تنفيذ استفسارك ولا يمكنك الانتظار حتى يعمل الآخرون ، فقتلهم باستخدام معرف مؤشر ترابط MySQL:

kill 5341773

(من داخل الخلية ، وليس شل ، من الواضح)

يجب عليك العثور على معرفات سلاسل الرسائل من:

show engine innodb status\G

الأمر ، ومعرفة أي واحد هو الذي يحجب قاعدة البيانات.

7
Ellert van Koperen

يمكنك استخدام:

show full processlist

والتي سوف تسرد جميع الاتصالات في الخلية والحالة الحالية للاتصال وكذلك الاستعلام الذي يجري تنفيذه. يوجد أيضًا متغير أقصر show processlist; يعرض الاستعلام المقطوع بالإضافة إلى إحصائيات الاتصال.

6
Gerrit Brink

إليكم ما اضطررت في النهاية إلى القيام به لمعرفة "استعلام آخر" الذي تسبب في مشكلة مهلة القفل. في رمز التطبيق ، نقوم بتتبع جميع استدعاءات قاعدة البيانات المعلقة على سلسلة رسائل منفصلة مخصصة لهذه المهمة. إذا استغرقت أي مكالمة DB أطول من N-seconds (بالنسبة لنا فهي 30 ثانية) ، فإننا نقوم بتسجيل الدخول:

// Pending InnoDB transactions
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started; 

// Optionally, log what transaction holds what locks
SELECT * FROM information_schema.innodb_locks;

مع ما سبق ، تمكنا من تحديد الاستعلامات المتزامنة التي أغلقت الصفوف التي تسببت في حالة توقف تام. في حالتي ، كانت عبارات مثل INSERT ... SELECT والتي على عكس SELECTs العادية تقفل الصفوف الأساسية. يمكنك حينئذٍ إعادة تنظيم الكود أو استخدام عزل معاملة مختلف مثل قراءة غير ملتزم بها.

حظا سعيدا!

4
Slawomir

إذا كنت تستخدم JDBC ، فسيكون لديك الخيار
includeInnodbStatusInDeadlockExceptions = صحيح

https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html

1
th3sly