昨天实易软件梁总提了一个问题:咱们探讨一个网络版软件的审批、打印、过账等控制机制。
假设张三填制了一张销售出库单已保存。保存后发现单据有错误或不完整,点了修改按钮开始修改。
李四有审批权限,在自己的电脑上看到了这张销售单,点了审批同意按钮签了字。
张三修改了错误,又增加了几个单品,用时较长,修攻完成后点了保存按钮。
张三修改后保存时,其实李四已经在张三修改期间审批签过字了。
这就造成,李四审批或审核签字前后的单据是不一样的。
审批是如此,打印也是如此,李四打印出的单据与电脑里保存的单据不一致。财务过账也是如此,记账凭证与原始单据不一致。
狐友会微信群里面讨论非常热烈,大家提出了很多自己的看法。猫猫今天花时间疏理一下,问题实际有两个,一个是张三改,李四审并发修改的控制,一个是打印的单据与数据库不一致的问题。先来看第一个问题,并发修改的控制,大家也分有两个方式:
1 时间戳方式(乐观锁)
2 锁表或锁行的方式(悲观锁)
在需要控制的表中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp),这个字段只要数据行任意字段发生了修改,时间戳就会发生改变。利用这个特性,无论是张三还是李四,在修改保存的候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致说明当前数据没有发生更改,可以保存,否则就是更新冲突。提示当前单据已经被他人修改过,无法更新。
时间戳非常好用,可以拦截掉一切变动。
此时还是存在一个问题:这个提示不够人性化,那我们追加一个提示:时间戳检查之后,再检查一下是不是被审掉,如果被审掉,提示被谁审核掉,这样就完美了。
其实这也是属于版本号控制,时间戳的好处是系统会自动改变时间戳。
当事务在操作数据时把这部分数据进行锁定,直到操作完毕后再解锁,其他事务操作才可操作该部分数据。这将防止其他进程读取或修改表中的数据。
如果李四先于张三打开了单据,张三连打开这张单据都不行,同一时刻只有一张单据可以被操作。反之亦然。
MSSQL用WITH (UPDLOCK)加锁
select * from 单据 WITH (UPDLOCK) WHERE Id =1
MYSQL用FOR UPDATE加锁
SELECT * FROM单据WHERE id=1 FOR UPDATE;
加锁当然也要配上事务去处理,不然不会达到想要的并发控制的效果。
事务开始
加锁
事务结束
也有人喜欢用一张表来记录张三和李四的操作,控制谁先谁后的问题,但这样会存在,张三打开了单据,张三掉线了,但单据还是张三的编辑状态,这样必须等张三上线,重新打开单据,再退出单据,才能消除单据的编辑状态(FOR 张三)
所有的并发控制控制的其实是修改,无论是时间戳还是加锁都是间接控制。那我们记录一下修改是不是就行了,sys(2017) 生成一个校验和,只要每一行的某一字段发生了变更,那么校验和就会发生变化,那利用这个特性不就可以实现并发控制了。
无论是张三还是李四,在修改保存的候检查当前数据库中数据的校验和与自己更新前取到的校验和进行对比,如果一致说明当前数据没有发生更改,可以连同校验和一起保存,否则就是更新冲突。提示当前单据已经被他人修改过,无法更新。
参考我的文章:sys2017在数据处理与同步中的应用
https://mp.weixin.qq.com/s/_6dh7t70zuoKr9T6EKEqkg
BTW:打印出来的单据,实际可以加一个二维码,通过扫码连接BS系统用来验证票据是否有效。
悲观锁一定成功,但在并发量特别大的时候会造成很长堵塞甚至超时,仅适合小并发的情况。
乐观锁不一定每次都修改成功,但能充分利用系统的并发处理机制,在大并发量的时候效率要高很多。
商城里面的抢购并发,就不适用于上面的模型了,等待猫猫下一篇更文。.