開發反饋,某業務系統插入一條記錄的時候,日志報錯,插入失敗:
### Error updating database. Cause: java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction ### The error may involve defaultParameterMap ### The error occurred while setting parameters ### SQL: INSERT INTO ...
登錄mysql,使用show processlist查看沒有發現相關會話的存在。然后使用show engine innodb status也沒有最近的死鎖信息。
至此,可以猜測,因為變量innodb_lock_wait_timeout的緣故,插入失敗的會話已經結束。
以下是變量innodb_lock_wait_timeout的用途說明:
innodb事務等待行鎖的時間,單位是秒,等待超過這個時間后就會放棄。默認是50秒。嘗試訪問被另一個InnoDB事務鎖定的行的事務在發出以下錯誤之前最多要等待這么多秒才能對該行進行寫訪問:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
發生鎖等待超時時,將回滾當前語句(而不是整個事務)。要使整個事務回滾,請使用--innodb-rollback-on-timeout選項啟動服務器。另請參見第14.21.4節“ InnoDB錯誤處理”。對於高度交互式應用程序或OLTP系統你可能需要降低該參數的值,給用戶反饋或者將更新到隊列中供以后處理。可以為長時間運行的后端操作(例如,數據倉庫中等待其他大型插入或更新操作完成的轉換步驟)增加此值。
innodb_lock_wait_timeout僅適用於InnoDB行鎖。MySQL表鎖不會在InnoDB內部發生,並且此超時不適用於等待表鎖。
鎖等待超時值不適用於死鎖,因為InnoDB會立即檢測到它們並回滾其中一個死鎖的事務。請參見第14.7.5.2節“死鎖檢測和回滾”。
innodb_lock_wait_timeout可以用SET GLOBAL或SET SESSION語句運行時設置。更改GLOBAL設置需要足夠的特權來設置全局系統變量(請參見第5.1.8.1節“系統變量特權”),並影響隨后連接的所有客戶端的操作。任何客戶端都可以更改innodb_lock_wait_timeout的SESSION設置,這僅影響該客戶端。
順便查看了一下數據庫中其它事務:
>SELECT * FROM information_schema.INNODB_TRX\G *************************** 1. row *************************** trx_id: 5877143864 trx_state: RUNNING trx_started: 2019-11-02 16:43:24 trx_requested_lock_id: NULL trx_wait_started: NULL trx_weight: 0 trx_mysql_thread_id: 10010454 trx_query: NULL trx_operation_state: NULL trx_tables_in_use: 0 trx_tables_locked: 0 trx_lock_structs: 0 trx_lock_memory_bytes: 360 trx_rows_locked: 0 trx_rows_modified: 0 trx_concurrency_tickets: 0 trx_isolation_level: REPEATABLE READ trx_unique_checks: 1 trx_foreign_key_checks: 1 trx_last_foreign_key_error: NULL trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 9996 trx_is_read_only: 0 trx_autocommit_non_locking: 0
這里看到,事務5877143864狀態是running,但是trx_query卻為null。光從這里看不到執行的sql,也不知道具體在哪個對象上加了鎖。
通過show engine innodb status也只是能看到該事務,但是看不到該事務的詳細信息。
這里需要明白的是,如果一個會話(連接)里面有未提交事務,然后不做任何操作,那么這個線程處於Sleep狀態。這也是為何通過show processlist查看,對應的線程也是處於sleep狀態的原因。
通過以下查詢,看看該會話最后執行的sql是什么內容:
SELECT a.sql_text, c.id, d.trx_started FROM PERFORMANCE_SCHEMA.events_statements_current a JOIN PERFORMANCE_SCHEMA.threads b ON a.thread_id = b.thread_id JOIN information_schema.PROCESSLIST c ON b.processlist_id = c.id JOIN information_schema.innodb_trx d ON c.id = d.trx_mysql_thread_id WHERE c.id = 10010454 ORDER BY d.trx_started\G;
查出的結果語句也是一個插入語句,與報錯的語句居然是相同功能的語句。那就正好可以分析一下,上面應用為何報錯了。
原因其實也很簡單,就是該會話執行插入后,事務沒有及時提交。導致后續的插入在插入唯一鍵時候阻塞了(這里涉及到跟開發溝通才了解的業務邏輯,本身設計存在問題以及唯一鍵問題)。