事务一开始就加锁,读加读锁,写加写锁
下面a和b是事务,d和e是数据。
读未提交:a写完d没提交就释放写锁,b读到d,造成脏读;
读已提交:b读取d,事务a修改/删除d提交后再释放写锁,此时不可读取此数据,
所以读到的是快照(?),b再读d,数据不一致,造成不可重复读;
可重复读:修改快照级别成事务级别,此时没有e,b能到读取d,
事务a修改/删除d写完提交后再释放写锁,b再读d,此时快照不变,d不变;
但是如果b insert e,a读取会读到e,造成虚读;
序列化:串行,某事物增删改查某表时,其他事务无法增删改查该表,
或者只能开启一个事务?
乐观锁和悲观锁:
用来解决不同事物修改同一个数据的问题,比如a、b依次开启,b修改d提交后a修改d提交。
乐观锁:修改提交过程添加一个版本,b修改提交后a的版本不对,所以a失败;
悲观锁:a加上写锁,b事务无法增删改查a,只能读取快照。
作者:沈杰
链接:https://www.zhihu.com/question/23242151/answer/132403394
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别。隔离性分为四个级别:1读未提交:(Read Uncommitted)2读已提交(Read Committed) 大多数数据库默认的隔离级别3可重复读(Repeatable-Read) mysql数据库所默认的级别4序列化(serializable)四个级别的具体实现和不同的请下面细读:首先程序是可以并发执行的,同样,在MySQL中,一个表可以由两个或多个进程同时来读写数据,这是没有问题的。比如,此时有两个进程来读数据,这也没什么问题,允许。但是如果一个进程在读某一行的数据的过程中,另一个在进程又往这一行里面写数据(改、删),那结果会是如何?同样,如果两个进程都同时对某一行数据进行更改,以谁的更改为准?那结果又会怎样,不敢想象,是不是数据就被破坏掉了。所以此时是冲突的。既然会冲突就要想办法解决,靠谁来解决,这时候就是靠锁机制来维护了。怎么使用锁来使他们不冲突?在事务开始的时候可以给要准备写操作的这一行数据加一个排它锁,如果是读操作,就给该行数据一个读锁。这样之后,在修改该行数据的时候,不让其他进程对该行数据有任何操作。而读该行数据的时候,其他进程不能更改,但可以读。读或写完成时,释放锁,最后commit提交。这时候读写就分离开了,写和写也就分离开了。注意:此时加锁和释放锁的过程由mysql数据库自身来维护,不需要我们人为干涉。mysql开发者给这个解决冲突的方案起了一个名字叫做:读未提交:(Read Uncommitted)。这也就是事务的第一个隔离性。但是这个程度的隔离性仅仅是不够的。看下面的测试结果:1)A修改事务级别为:未提交读。并开始事务,对user表做一次查询
https://segmentfault.com/a/1190000015738121
乐观锁和悲观锁
无论是Read committed还是Repeatable read隔离级别,都是为了解决读写冲突的问题。
单纯在Repeatable read隔离级别下我们来考虑一个问题:
此时,用户李四的操作就丢失掉了:
丢失更新:一个事务的更新覆盖了其它事务的更新结果。
(ps:暂时没有想到比较好的例子来说明更新丢失的问题,虽然上面的例子也是更新丢失,但一定程度上是可接受的..不知道有没有人能想到不可接受的更新丢失例子呢...)
解决的方法:
使用Serializable隔离级别,事务是串行执行的!
乐观锁
悲观锁
乐观锁是一种思想,具体实现是,表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样更新,反之拒绝。之所以叫乐观,因为这个模式没有从数据库加锁,等到更新的时候再判断是否可以更新。
悲观锁是数据库层面加锁,都会阻塞去等待锁。
2.3.1悲观锁
所以,按照上面的例子。我们使用悲观锁的话其实很简单(手动加行锁就行了):
select * from xxxx for update
在select 语句后边加了 for update相当于加了排它锁(写锁),加了写锁以后,其他的事务就不能对它修改了!需要等待当前事务修改完之后才可以修改.
也就是说,如果张三使用select ... for update,李四就无法对该条记录修改了~
2.3.2乐观锁
乐观锁不是数据库层面上的锁,是需要自己手动去加的锁。一般我们添加一个版本字段来实现:
具体过程是这样的:
张三select * from table ---#会查询出记录出来,同时会有一个version字段
李四select * from table ---#会查询出记录出来,同时会有一个version字段
李四对这条记录做修改: update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},判断之前查询到的version与现在的数据的version进行比较,同时会更新version字段
此时数据库记录如下:
张三也对这条记录修改: update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},但失败了!因为当前数据库中的版本跟查询出来的版本不一致!