【问题标题】:Constant Lock Wait Timeout with MySQL InnoDB tableMySQL InnoDB 表的恒定锁定等待超时
【发布时间】:2017-10-29 05:13:28
【问题描述】:

我在使用这样创建的 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 - Lock wait timeout exceeded; try restarting transaction”

该表中的记录永远不会超过 120 条,但它会受到 SELECT、UPDATE 和 DELETE 语句的严重影响。非常基本的查询主要在 tableID 上进行过滤,但在一些选择语句中连接到少于 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);

【问题讨论】:

  • 我删除了“死锁”标签。锁等待不是死锁。
  • 您真的需要 2 个唯一键吗?这可能会使所涉及的锁数量增加一倍。
  • 看看并发SELECTs.
  • 添加了查询日志,所有选择在 +- 等待超时内命中表。我注意到我在

标签: mysql locking innodb wait


【解决方案1】:

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

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

例如,如果会话 1 执行以下操作:

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

此事务将锁定 colID > 5678 的所有行,包括最后的间隙。这通常是块插入的内容。

请参阅InnoDB Locking: Gap Locks 以了解有关间隙锁的一些信息。

您可以通过将事务隔离级别设置为 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;

做你的cmets。

每个语句都使用一个事务。如果您不显式控制事务的开始和结束,您可能会使用 autocommit,其中每个语句都会隐式启动事务并在语句执行完成后立即提交事务。因此,您的 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 具有执行 locking reads 的选项,但您不会在显示的 SELECT 语句中显示任何这些选项。

还要仔细检查您的配置选项 innodb_lock_wait_timeout 的值(点击链接了解更多信息)。默认值为 50 秒,但如果有人将其设置为非常小的值(如 0),则可能会导致虚假超时。

mysql> SHOW GLOBAL VARIABLE LIKE 'innodb_lock_wait_timeout';

【讨论】:

  • 嗨,比尔,感谢您回复和帮助处理标签!我没有在我的查询中指定 START/COMMIT 事务 [只是肮脏的 UPDATE/INSERTS](来自多个 C++ 应用程序)。我查看了我的 SQL 包装器,也没有看到任何与事务相关的东西。数据库级别的“tx_isolation”变量设置为“REPEATABLE-READ”,因此我将其更改为“READ-COMMITTED”并进行报告。此外,我的 UPDATE 语句正在使用相等的语句运行,而不是针对范围,如果这有影响的话?
  • 我的意思是“等于语句”是 = 符号过滤器。例如: UPDATE TableX SET ColX = value WHERE ColID = xxx
  • 希望你没有使用autocommit=0
  • 自动提交已打开,我将尝试在过滤的列上创建索引,如果这没有帮助,我将发布我的 C++ 代码。感谢您的建议!
  • 我已经添加了 C++ 代码,因为我仍然遇到问题。开始使用 innotop 尝试诊断,但到目前为止没有运气。如果还需要什么,请告诉我。此外,如果无法弄清楚这一点,您是否知道一个好的 MSSQL C++ 库?谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-24
  • 2011-01-07
  • 2011-12-25
  • 1970-01-01
  • 2011-09-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多