繁体   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