先读判空后写的并发问题

近期有业务需求:先读,判断某分类名是否存在,不存在就插入该分类名。将这个流程放到事务里后,就以为万事大吉了。结果用户并发操作时,发生了DuplicateKeyException(门店ID + 分类名有唯一键)。

系统排查了一下数据库(MySQL InnoDB)的事务原理后,确认了为什么放到事务里没有解决。

mysql的innodb数据库,默认事务配置是repeatable read,只保证了读取一致性,默认select不加锁。
所以,尽管(先读判空再写)放事务里,但是两次并发读没有锁,两次写就都会尝试,发生DuplicateKeyException。
为了给select加锁,常见操作是select for update,会尝试进行record锁,如果查询命中了某个条目,会加互斥锁,解决其他事务的访问问题。
但是此处的业务逻辑是先读,为空则写。所以即便使用select for update(会尝试进行record锁),在没命中时会变为gap锁,而gap锁不像record锁,不存在互斥,所以两次并发请求还是都会尝试写。
不想抛出DuplicateKeyException,能想到的办法
    1. 由于数据是租户隔离的,如果能接受写入的时候,该租户不能读到分类数据,可以尝试写入时,将该租户的全部数据锁定。(基于ukey是租户+分类)
    2. 引入分布式锁(利用redis等实现),写前读取时先锁定租户。
两个方式成本都比较高。
即便发生了DuplicateKeyException,也不会真正影响业务,可以暂时接受,catch住给用户返回易懂信息。

发布了66 篇原创文章 · 获赞 17 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/ligeforrent/article/details/83002568