簡體   English   中英

MySQL InnoDB表的恆定鎖定等待超時

[英]Constant Lock Wait Timeout with MySQL InnoDB table

我在創建這樣的MySQL InnoDB表時遇到鎖等待超時的可怕問題:

CREATE TABLE `TableX` (
  `colID` int(10) unsigned NOT NULL DEFAULT '0',
  `colFK` int(10) unsigned NOT NULL DEFAULT '0',
  `colX` smallint(5) unsigned NOT NULL DEFAULT '0',
  `colX` int(10) unsigned NOT NULL DEFAULT '0',
  `colX` smallint(5) unsigned NOT NULL DEFAULT '0',
  `colX` int(10) unsigned NOT NULL DEFAULT '0',
  `colX` smallint(5) unsigned NOT NULL DEFAULT '0',
  `colX` binary(20) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
  `colX` int(10) unsigned zerofill NOT NULL DEFAULT '0000000000',
  `colX` smallint(5) unsigned NOT NULL DEFAULT '0',
  `colX` int(10) unsigned zerofill NOT NULL DEFAULT '0000000000',
  `colX` smallint(5) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`colFK`),
  UNIQUE KEY `colID` (`colID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

錯誤如下:“ [Err] 1205-超出鎖定等待超時;嘗試重新啟動事務”

該表中的記錄永遠不會超過120條,但是會受到SELECT,UPDATE和DELETE語句的嚴重破壞。 非常基本的查詢主要根據tableID進行過濾,但在某些select語句中將其聯接到少於2000條記錄的其他表。 我已經測試了所有選擇查詢,它們執行所需的時間不到100-200毫秒。

發生問題時,InnoDB Status將返回以下內容:

---TRANSACTION 2605217, ACTIVE 1 sec inserting
 mysql tables in use 1, locked 1
 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
 MySQL thread id 11826, OS thread handle 4104, query id 1940531 xxxx xxxxx xxxx update
 INSERT INTO TableX(cols) VALUES(values)
 ------- TRX HAS BEEN WAITING 1 SEC FOR THIS LOCK TO BE GRANTED:
 RECORD LOCKS space id 227 page no 3 n bits 168 index PRIMARY of table `TableX` trx id 2605217 lock mode S locks rec but not gap waiting
 Record lock, heap no 97 PHYSICAL RECORD: n_fields 14; compact format; info bits 32

常規查詢日志顯示4個選擇,並在一秒鍾內發生一次插入。 INSERT是事務因鎖定等待超時而失敗。 所以我的問題是,我該怎么辦? 我嘗試過重新配置服務器,重新安裝MySQL,更改事務級別。

如果格式設置不正確,我深表歉意。我無法將創建表放入代碼塊中。 隨時編輯我的帖子或要求提供更多信息。 謝謝!

編輯:添加一般查詢日志+等待超時

2017-05-02T02:06:26.443095Z 12195 Query SELECT SQL_BUFFER_RESULT * FROM TableX LEFT JOIN TableY USING (ColA) LEFT JOIN TableA USING (ColA) LEFT JOIN TableZ USING (ColA) LEFT JOIN TableH USING (ColA) LEFT JOIN TableI USING(ColA) WHERE UnindexedCol IS NOT NULL AND UnindexedColB <= 0  ORDER BY UnindexedCol ASC
2017-05-02T02:06:26.708769Z 11829 Query SELECT * FROM TableX LEFT JOIN TableA ON TableX.ColA = TableA.ColA WHERE UnindexedCol = 'text' LIMIT 1
2017-05-02T02:06:27.021306Z 11826 Query SELECT * FROM TableX WHERE IDColA = 1000
2017-05-02T02:06:27.068185Z 11826 Query INSERT INTO TableX(cols) VALUES(values)
2017-05-02T02:06:27.224393Z 11829 Query SELECT colList, MIN(ColA) FROM TableX JOIN TableY USING (ColA) WHERE IF (IDColE <> 0, IDColE = (SELECT MAX(IDColE) FROM TableY WHERE IDColF = 22073), IDColF = 22073) GROUP BY UnIndexedColS, UnIndexedColT
2017-05-02T02:06:27.224393Z  1697 Query Show engine innodb status
2017-05-02T02:06:27.224393Z  1696 Query SELECT st.* FROM performance_schema.events_statements_current st JOIN performance_schema.threads thr ON thr.thread_id = st.thread_id WHERE thr.processlist_id = 1697
2017-05-02T02:06:27.224393Z  1696 Query SELECT st.* FROM performance_schema.events_stages_history_long st WHERE st.nesting_event_id = 211
2017-05-02T02:06:27.224393Z  1696 Query SELECT st.* FROM performance_schema.events_waits_history_long st WHERE st.nesting_event_id = 211
2017-05-02T02:06:28.224501Z 11829 Query SELECT ColList FROM TableX WHERE UnIndexedCol = 2 OR UnIndexedCol = 2 GROUP BY ColList

這是用於調用查詢的C ++代碼:

*  Executes a query.                                                    *

int32 Sql_Query(Sql_t* self, const char* query, ...)
{
    int32 res;
    va_list args;

    va_start(args, query);
    res = Sql_QueryV(self, query, args);
    va_end(args);

    return res;
}

*  Executes a query.                                                    *

int32 Sql_QueryV(Sql_t* self, const char* query, va_list args)
{
    if( self == NULL )
        return SQL_ERROR;

    Sql_FreeResult(self);
    StringBuf_Clear(&self->buf);
    StringBuf_Vprintf(&self->buf, query, args);
    if( mysql_real_query(&self->handle, StringBuf_Value(&self->buf), (uint32)StringBuf_Length(&self->buf)) )
    {
        ShowSQL("DB error - %s\n", mysql_error(&self->handle));
        ShowSQL("Query: %s\n", StringBuf_Value(&self->buf));
        return SQL_ERROR;
    }
    self->result = mysql_store_result(&self->handle);
    if( mysql_errno(&self->handle) != 0 )
    {
        ShowSQL("DB error - %s\n", mysql_error(&self->handle));
        ShowSQL("Query: %s\n", StringBuf_Value(&self->buf));
        return SQL_ERROR;
    }
    return SQL_SUCCESS;
}

int     STDCALL mysql_real_query(MYSQL *mysql, const char *q,
                    unsigned int length);

MYSQL_RES * STDCALL mysql_store_result(MYSQL *mysql);

您能做的最好的事情就是及時完成交易。

鎖定持續時間與查詢執行的速度無關。 關於鎖保持多長時間。 鎖定將保持到事務提交或回滾為止。

例如,如果會話1執行以下操作:

START TRANSACTION;
UPDATE TableX SET colX = 1234 WHERE colID >= 5678;

本次交易將持有的所有行鎖與colID> 5678, 包括在最后的差距 這通常是阻止插入的內容。

請參閱InnoDB鎖定:間隙鎖,以了解有關間隙鎖的一些知識。

您可以通過將事務隔離級別設置為READ COMMITTED來避免大多數間隙鎖定,但是請確保這對於您的應用程序在邏輯上的需求是可以的。

您還可以通過在代碼執行任何需要花費無限時間的事情之前解決事務來解決此問題。 我的意思是(偽代碼):

start transaction;
do some sql query that acquires locks;
post data to a web service that takes 500ms to respond;
commit;

以上將不必要地將鎖保持半秒鍾。 如果您有十多個並發運行,那么最后一個將等待> 6秒,因為它必須等待所有排隊的人之前。 如果您有更多,他們將等待更長的時間。

最好這樣做:

start transaction;
do some sql query that acquires locks;
commit;
post data to a web service that takes 500ms to respond;

重新發表您的評論。

每個語句都使用一個事務。 如果您不控制事務的顯式啟動和結束,則可能使用自動提交 ,其中每個語句隱式啟動事務並在語句執行完成后立即提交事務。 因此,也許您的SQL語句花費的時間太長。

另一個想法:您的SQL查詢使用的是針對未索引列的搜索。 我在您的示例表中看到,您將colID作為PK,將colFK作為外鍵(始終被索引)。 如果您搜索其他任何列,則必須進行表掃描以進行搜索,這意味着它鎖定了它檢查的每一行。 如果使用索引來幫助您的搜索,它還將使需要鎖定的行數最小化,這將大大有助於並發更新。


使用查詢和C ++代碼重新進行更新。

INSERT會導致鎖定,但它們的范圍應小而簡短。 我們在SHOW ENGINE INNODB STATUS中看到,您的INSERT正在等待訪問表的主鍵。 因此,其他一些線程必須將其鎖定。

當您看到鎖定問題時,可以查詢INFORMATION_SCHEMA.INNODB_LOCK_WAITS表以查看哪些事務正在等待以及哪個事務正在使它們等待(即阻塞)。 僅當您在鎖定等待仍在等待時查詢時,此方法才有效。 參見https://dev.mysql.com/doc/refman/5.7/en/innodb-lock-waits-table.html

您的大多數查詢都是SELECT語句,它們是非鎖定SELECT。 如果您同時執行INSERT / UPDATE / DELETE,則使用InnoDB表時,這些類型的查詢不會等待鎖定。 它們也不會阻塞其他線程。

如果您正在執行ALTER TABLE或使用顯式LOCK TABLES語句,則SELECT可以等待(或阻止)元數據鎖定。 但是您沒有提到您正在執行這兩種操作。

SELECT具有進行鎖定讀取的選項,但您不會在顯示的SELECT語句中顯示任何這些選項。

還要仔細檢查您的配置選項innodb_lock_wait_timeout的值 (單擊鏈接以了解有關此內容的更多信息)。 默認值為50秒,但是如果有人將其設置為非常小的值(如0),則可能導致虛假超時。

mysql> SHOW GLOBAL VARIABLE LIKE 'innodb_lock_wait_timeout';

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM