高并发事务管理死锁难题清查

业务管理系统发布后,服务项目日志出错:

Jul 20 15:10:30 xxx: {"level":"error","error":"Error 1213: Deadlock found when trying to get lock; try restarting transaction","time":"2021-07-20T15:10:35.845197649 08:00","message":"error delete entities before insert"}

上下游业务管理系统监视好几个topic,但不一样topic有相交,相交为一同升级大家系统软件的某一张表。服务项目尽管一直在出错,可是数据信息并沒有发生反复及遗失的状况。对于这个问题状况开展清查。

1 清查构思:

1.1 最先调查下mysql InnoDB锁的详细描述:

定义:

共享资源锁(S Lock):容许事务管理读一行数据信息,好几个事务管理能够 高并发对某一行数据信息加S Lock

排他锁(X Lock): 容许事务管理删掉或升级一行数据信息,仅有行数据信息沒有一切锁才能够 获得X Lock

共享资源锁和排他锁,便是大家日普遍到的读锁和写锁。一个进程加了读锁,别的进程如果是获取数据,还可以加读锁再次载入。而一旦有一个进程必须 加写锁,前提条件是该数据信息沒有上锁,假如当今数据信息早已加了读锁或是写锁,当今进程务必直到锁释放出来,才能够 加写锁。

共享资源锁和排他锁,在InnoDB中相匹配的是行等级锁。可是InnoDB除开适用共享资源锁(S Lock)和排他锁(X Lock),还适用表等级的俩把锁,意愿共享资源锁(IS Lock)和意愿排他锁(IX Lock),意愿共享资源锁和意愿排他锁尽管是表等级的锁具体运用内行级锁当中,用于锁住一个小范畴。IS Lock事务管理要想得到 一张表格中某两行的共享资源锁; IX Lock事务管理要想得到 一张表格中某两行的排他锁

  • 行锁 :锁住一行数据信息,即大家普遍的共享资源锁和清查锁
  • 空隙锁:锁住一个范畴,但不包含纪录自身。比如数据库查询中id为3,8,11,那麼锁住的区段很有可能为(-∞, 3), (3, 8), (8, 11), (11, ∞)。倘若插进的数据信息id为6,那麼这时锁住的区段为(3, 6), (6, 8)被锁住,不包括要插进的6
  • 行锁 空隙锁:锁住一个范畴,包含纪录自身。比如数据库查询中id为3,8,11,那麼锁住的区段很有可能为(-∞, 3], (3, 8], (8, 11], (11, ∞]。那麼倘若插进id为6的数据信息,这时锁住的区段为(3, 6], (6, 8]2个一部分,能够 见到,6也被锁住了。

1.2 空隙锁有什么作用?

大家了解了MySQL的InnoDB的普遍锁,了解了表等级空隙锁会运用内行等级的范畴当中。那麼空隙锁有哪些好处呢。

大家应当听闻过幻读,即在同一事务管理下,持续实行2次一样的SQL句子很有可能造成不一样的結果,第二次的SQL句子很有可能回到以前不会有的行。InnoDB应用行锁 空隙锁的方法处理这个问题。自然InnoDB储存模块在查看数据信息时是不会有锁的,这是由于查看的数据信息来自于快照更新版本号,即历史记录。

1.3 MySQL普遍实际操作对锁的运用

  • Insert实际操作:数据库查询插入一行数据信息时,必须 获得行锁
  • Update实际操作:升级一条纪录时,假如纪录存有,必须 行锁,假如不会有,必须 行锁 空隙锁。
  • Delete实际操作:删掉一条纪录时,假如纪录存有,必须 行锁;假如纪录不会有,行锁 空隙锁。
  • Select实际操作:不容易上锁,由于查看的数据信息关键来自于快照更新版本号,即历史记录。除非是表明的启用lock share mode或for update。
-- 表明的为查看加上共享资源锁S Lock
select * from a where id = 1 lock in share mode ;
-- 表明的为查看加上排他锁X Lock
select * from a where id = 1 for update ;

1.4 服务项目为什么会Deadlock

根据早期对Mysql InnoDB锁相关资料的掌握,剖析大家系统软件为什么会发生很多的deadlock日志出错。

Jul 20 15:10:30 xxx: {"level":"error","error":"Error 1213: Deadlock found when trying to get lock; try restarting transaction","time":"2021-07-20T15:10:35.845197649 08:00","message":"error delete entities before insert"}

deadlock原因

导致死锁市场竞争情况后,mysql会将优先选择的事务管理递交,另一个事务管理释放出来锁,随后抛出去出错信息内容。

2 处理构思

高并发状况下降低delete-insert事务管理实际操作

能够 逃避这类在事务管理中,delete-insert线程同步实际操作的难题,比如我们可以应查数据信息是不是存有,不会有不实行delete实际操作,防止不会有实行delete实际操作,开启mysql的行锁 空隙锁体制。假如存有大家delete,只能采用mysql的行锁。这就一定水平上防止了锁市场竞争没法释放出来的难题。可是那样实际操作也会存有一定的风险性,是不是能够 软删掉,防止分布式系统状况下,发生数据信息早已被删掉,而别的事情已经删掉不会有的数据信息难题。

单过程下可考虑到在事务管理上上锁

sessionA和sessionB2个事务管理,在市场竞争的状况下,删除了不会有的纪录,会开启mysql的行锁 空隙锁。关键立足点取决于,与其说在mysql市场竞争空隙锁的全过程中出错,随后事务管理回退,資源很多消耗,比不上在进到事务管理以前开展高并发操纵。尽管锁的粒度分布有点儿粗,可是相对性于事务管理一直回退,服务器端不断打印错误日志,是更能接纳的。

多进程高可用性的状况

针对高可用性多进程状况,能够 根据分布式锁结果。假如不愿依靠非mysql的外界锁结果,那麼还可以考虑到对delete-insert事务管理开展排列,添加井然有序序列中,逐个消化吸收。这本质上也是变向干了同步控制。

思索方位:尽量防止开启mysql的空隙锁。

3 最后解决方案

单过程加了一个锁,对线程同步的delete-insert事务管理,同歩解决。

// 对进程高并发启用的方式
func (ei entitiesImpl) UpsertEntitis(ctx context.Context, id string, entities model.Entities) error {
	conn, err := DB.Conn(ctx)
	if err != nil {
		return err
	}
	defer conn.Close()
	// 对delete-insert做同歩解决
	entityMux.Lock()
	defer entityMux.Unlock()
	tx, err := conn.BeginTx(ctx, &sql.TxOptions{})
	if err != nil {
		return nil
	}
	res, err := tx.ExecContext(ctx, "delete from entities  where id = ?", id)
	if err != nil {
		tx.Rollback()
		return err
	}
	_, _ = res.RowsAffected()
	for _, v := range entities {
		_, err := tx.ExecContext(ctx, "INSERT INTO entities (`id`) VALUES (?)",v.id)
		if err != nil {
			tx.Rollback()
			return err
		}
	}
	tx.Commit()
	huskar.Debug(ctx).Int("entities_size", len(entities)).Msg("insert new entities")
	return nil
}

评论(0条)

刀客源码 游客评论