MySQL InnoDB 表的恒定锁定等待超时

问题描述 投票:0回答:2

我在创建 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 语句中连接到其他记录少于 2,000 条的表。我已经测试了所有选择查询,它们的执行时间不到 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);
mysql locking innodb wait
2个回答
4
投票

对此您能做的最好的事情就是立即完成交易。

锁定持续时间与查询执行的速度无关。这是关于锁保持多长时间的问题。锁定将一直保持到事务提交或回滚为止。

例如,如果会话 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。当您使用 InnoDB 表时,如果您只是同时执行 INSERT/UPDATE/DELETE,这些类型的查询不会等待锁。它们也不阻塞其他线程。

如果您正在执行 ALTER TABLE,或者使用显式

LOCK TABLES

语句,

SELECT 可以等待(或阻止)元数据锁。但你没有提到你正在做其中任何一个。 SELECT 具有执行

锁定读取

的选项,但您不会在显示的 SELECT 语句中显示任何这些选项。 还要仔细检查配置选项的值

innodb_lock_wait_timeout

(请点击链接阅读有关此内容的更多信息)。默认值为 50 秒,但如果有人将其设置为非常小的值(例如 0),则可能会导致虚假超时。 mysql> SHOW GLOBAL VARIABLE LIKE 'innodb_lock_wait_timeout';



0
投票

将父函数的交易发送到内部更新函数中使用,解决了问题。

© www.soinside.com 2019 - 2024. All rights reserved.