假设拿医院系统的药品部分举例, 药品表结构大概如下:
现在有多个地方可能会去操作药品的数量:
因为在系统里可能某些药的使用频率是非常高的,比如一些生理盐水、葡萄糖之类的。那么就经常会遇到,护士在生成请领单事务还没完成,药房正在发的药中也含有这些药,这样互相等待直接就被 mysql 的死锁检测机制检测到。
想请问大家遇到这种问题一般会采用什么样的方案来解决?
不知大家是否还有其他比较好的解决方案
1 henry19890701 2017-06-21 22:20:50 +08:00 ![]() 保证加锁顺序就可以了,代码应该不怎么需要改 |
![]() | 2 letitbesqzr OP @henry19890701 那其实也就只有让他同步,单线程的去减存库? |
3 Mirana 2017-06-21 22:56:27 +08:00 ![]() 加锁失败回滚 然后重试 |
![]() | 4 wind3110991 2017-06-21 23:10:05 +08:00 ![]() 感觉这个最主要的问题并不是事务或者是锁,而是你的流程问题: 为什么操作失败就要 回滚到 请领单状态? 建议如果并发量大容易锁,流程中可以穿插多个原子事务,失败回滚到一个中间状态,而不是后台一个接口大包大揽。 事务不是万能的,要符合你的当前使用场景才行 |
5 billlee 2017-06-21 23:12:37 +08:00 ![]() @letitbesqzr #2 这个肯定是要同步的。别说是数据库,就算是内存里的变量,多线程操作也要加锁进临界区啊。 |
![]() | 6 cjyang1128 2017-06-21 23:18:21 +08:00 ![]() 一般死锁问题都是通过保证加锁顺序实现的。除了楼主提的几个方案之外,可以把某些数据存储在 redis 中,因为 redis 是单线程的,所以不存在竞争问题。因为你本质上是 id=>数量的一个映射,所以也可以考虑一下。 |
![]() | 7 letitbesqzr OP @wind3110991 就比如发药 1. 将请领单状态设置为已发药 2. 各种计费改状态 3. 减少存库 4. 写存库流水 5. 记录日志 那么某一步失败肯定需要回滚到第一个状态,实际情况一个事务里还会做更多的操作,业务非常复杂 |
![]() | 8 letitbesqzr OP @wind3110991 意思就是,其实大事务拆成一个个短事务是比较常见的做法? |
![]() | 9 3dwelcome 2017-06-21 23:50:29 +08:00 ![]() 如果是我的话,就用单线程队列。拆分事务只能让逻辑变复杂。代码应该多遵循 KISS 原则,能简单处理的问题,别复杂化。 |
![]() | 10 ebony0319 2017-06-21 23:57:52 +08:00 via Android ![]() 如果是我我会采取队列方式。谁先就应该给谁,不应该抢资源。 |
![]() | 11 letitbesqzr OP |
![]() | 12 ryd994 2017-06-22 01:06:51 +08:00 via Android ![]() 一种药一个事务 A 药开不出和 B 药没有联系,不需要锁一起 |
![]() | 13 reus 2017-06-22 08:21:30 +08:00 ![]() 手工上锁 + 事务 |
![]() | 14 msg7086 2017-06-22 10:01:20 +08:00 ![]() 上锁超时跳过,回头来重试呗。 万一某个药没货了,病人别的药也不让吃了么…… |
![]() | 15 jianzhiyao020 2017-06-22 15:29:57 +08:00 ![]() 如果, 并发量不是特别大的话, 可以选择序列化事务隔离级别, 绝对不会死锁。 但是伴随来说, 速度会相应降低, 但是应该总比死锁好。 |
![]() | 16 letitbesqzr OP @jianzhiyao020 ![]() |
![]() | 17 jianzhiyao020 2017-06-22 15:40:32 +08:00 @letitbesqzr 那就要概念 hack 了, 例如葡萄糖放在一个地方, 大家都去拿,那是否是会增加堵塞的概率, 可以护士那里放一点, 药房那里放一点, 是不是就不会那么堵塞了, 好了, 我说那么多, 其实就是将葡萄糖分开几个记录存取。 |
![]() | 18 wind3110991 2017-06-22 22:13:37 +08:00 @letitbesqzr 如你说的,回滚没有必要从滚到 1 之前啊 比如你在 5 出错了,滚到 4 不就好了,记录下当前状态,在队列等待就好了啊 |
![]() | 19 ebony0319 2017-07-06 23:31:56 +08:00 via Android 最近又查了一些资料,好像都不满意,想问问你这个问题你们目前的思路么。 |
![]() | 20 letitbesqzr OP ![]() @ebony0319 1. 减短事务 2. 使用了 mq 队列进行增删存库 目前采用上面两种方案已经能够满足目前的请求量了,下面一个预备的方案 3. 存库的数量都丢到 redis 中 程序只是操作 redis 里面的存库 定期同步到数据库 |