Fuhui

MySQL事务(2)


花繁柳密能拨开方见手段,风狂雨骤时可立定才是脚跟。

引言

MySQL处理事务的过程中,遇到如下报错:

Error 1205: Lock wait timeout exceeded; try restarting transaction

结合日志信息,很快的定位了问题代码并做了修复,但这个报错却一直存在。观念里,只要等待一段时间,这个错误就应该消失啊,是哪里出问题了?

问题

代码在执行Begin之后,处理到某个逻辑直接return处理,没有关闭事务导致的。因为SQL操作的记录一直占着锁得不到释放,所以后续对该行记录进行写操作时,就会报这个错误。示例代码如下:

func (notify *Sign) HandleSign(ctx *context.Context) error {

	// 事务操作,开启事务
	if err := ses.Begin(); err != nil {
		return err
	}

	// 这里需要特别注意,正常情况必须加上
	// defer ses.Close()

	// 更新Log表记录
	_, err := ses.Where("id = ?", contractLog.Id).Cols("status").Update(contractLog)
	if err != nil {
		ses.Rollback()
		return err
	}

	//这个地方直接return导致的,这个事务没有关闭,导致上面的锁一直没有释放
	return nil

	if err := ses.Commit(); err != nil {
		return err
	}
	ses.Close()
}

问题的关键就在于Begin后,没有执行RollbackCommit,导致事务没有被关闭。这里特别强调defer close()的作用,下面是文档对Close的注释。

Close release the connection from pool

When Close be called, if session is a transaction and do not call Commit or Rollback, then call Rollback

PROCESSLIST

SHOW [FULL] PROCESSLIST

使用如上命令,查看是否存在一个线程执行占用了很长的时间,这体现在它Sleep的时间上。果不其然,确实有那么一个,但我忍住没有KILL它。因为我无法确定就是这个线程导致的。

SHOW PROCESSLIST shows which threads are running. If you have the PROCESS privilege, you can see all threads. Otherwise, you can see only your own threads (that is, threads associated with the MySQL account that you are using). If you do not use the FULL keyword, only the first 100 characters of each statement are shown in the Info field.

SHOW ENGINE INNODB STATUS

SHOW ENGINE INNODB STATUS displays extensive information from the standard InnoDB Monitor about the state of the InnoDB storage engine. For information about the standard monitor and other InnoDB Monitors that provide information about InnoDB processing, see Section 15.16, “InnoDB Monitors”.

一上来就引用SHOW ENGINE Syntax也是因为没有接触过这个命令,网上说:当你更新表中的某条记录时,如果一些别的线程对这条记录上了锁,并且执行占用时间较长的话,就会导致你对该条记录的操作超时。

不过非常遗憾,我查看了它输出的结果,并没有显示出这条问题SQL

innodb_lock_wait_timeout

show variables like 'innodb_lock_wait_timeout';

当事务没有被释放时,后续事务执行失败的等待时间就是由这个设置决定的。当事务访问这条记录超过这个时间还无法获得锁,就会报引言中的错误。

The length of time in seconds an InnoDB transaction waits for a row lock before giving up. The default value is 50 seconds. A transaction that tries to access a row that is locked by another InnoDB transaction waits at most this many seconds for write access to the row before issuing the following error

参考文章:

  1. stackoverflow problem