MySQL InnoDB表的常量锁定等待超时

时间:2017-05-29 00:28:59

标签: mysql locking innodb wait

我在使用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,更改事务级别..

如果格式化已关闭,我道歉我无法将create table放入代码块。随意编辑我的帖子或要求提供所需的更多信息。谢谢!

编辑:添加常规查询日志+ -wait timeout

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 个答案:

答案 0 :(得分:2)

您可以做的最好的事情是及时完成您的交易。

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

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

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

此事务将使用colID&gt;保留所有行的锁定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;

重新评论。

每个语句都使用一个事务。如果您没有显式地控制事务的开始和结束,那么您可能正在使用 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。如果您只是同时执行INSERT / UPDATE / DELETE,那么当您使用InnoDB表时,这些类型的查询不会等待锁定。它们也不会阻止其他线程。

如果您正在执行ALTER TABLE,或者您正在使用显式LOCK TABLES语句,则SELECT可以等待(或阻止)元数据锁定。但你没有提到你正在做其中任何一个。

SELECT具有执行locking reads的选项,但您不会在所显示的SELECT语句中显示任何这些选项。

同时仔细检查配置选项innodb_lock_wait_timeout的值(点击链接阅读更多相关信息)。默认值为50秒,但如果有人将此值设置为非常小的值(如0),则可能导致虚假超时。

mysql> SHOW GLOBAL VARIABLE LIKE 'innodb_lock_wait_timeout';