我们做一些互联网项目时,会有高并发的要求,使得数据库在一个多事务的环境下运行,多个事务之间就会互相影响,产生一系列问题,丢失更新是最常想到的问题。
丢失更新就是两个不同的事务(或者Java程序线程)在某一时刻对同一数据进行读取后,先后进行修改。导致第一次操作数据丢失。
一、第一类丢失更新
我们以甲乙两人同时操作同一账户里的钱来举这个例子:
时刻
甲
乙
t1
查询到账户余额为100元
t2
查询到账户余额为100元
t3
花费10块钱买了个棒棒糖
t4
提交,账户余额为90元
花费20块买了个铁锤
t5
后悔了,取消订单,事务回滚,账户余额为100元
以上的例子很容易解释清楚第一类丢失更新,也就是 A事务回滚时,把已经提交的B事务的更新数据覆盖了。但是这种丢失更新已经被数据库消灭掉了, 我们在使用的过程中是不会遇到了。
但是,本着探索求知的目的,我们需要搞清楚数据库实现了什么,让第一类丢失更新消失掉了。答案就是锁机制。
数据库的锁
锁可以分为乐观锁和悲观锁,而悲观锁又分为:读锁(共享锁)和写锁(排它锁),而数据库实现了悲观锁中的读锁和写锁,乐观锁需要自己实现。
数据库在设计这两种锁的时候,这两种锁间的关系如下:读锁与读锁可以共存,读锁与写锁互斥,写锁与写锁互斥。
1、读锁
共享锁(S锁):共享 (S) 用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
2、写锁
排他锁(X锁):用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。
如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
3、乐观锁
乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法乐观,乐观的认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新时,才去判断是否有冲突。
实现方式:
在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。具体如下:
1、查询需要修改的记录,获取version字段保存。
2、更新提交时,二次查询刚才的记录,获取出version字段;
3、比较第一次获取的version字段的值和第二次获取的值是否相等,
4、如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,并且更新成功将version字段的值加1;
5、如果不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
二、第二类丢失更新
还以上面的举例子:
时刻
甲
乙
t1
查询到账户余额为100元
t2
查询到账户余额为100元
t3
花费10块钱买了个棒棒糖
t4
提交,账户余额为90元
花费20块买了个铁锤
t5
提交,账户余额为80元
这种本来应该两个都成功,扣的款应该是两个人的总和,但因事务提交的时间不同,后面提交的数据把前面提交的数据覆盖掉了。也就是A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失,称之为第二类丢失更新。
第一类丢失更新数据库已经替我们解决了,但是第二类丢失更新数据库为我们提供不同的隔离级别去解决大部分问题。