mysql数据库锁机制事务级别悲观锁乐观锁

总结:
事务一开始就加锁,读加读锁,写加写锁
下面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表做一次查询<img src="https://pic2.zhimg.com/v2-f3f7a559c42064af5102ab95388d654d_b.jpg" class="content_image">2)B事务更新一条记录<img src="https://pic2.zhimg.com/v2-4ae7a68c02f2a64605faa60908e72e1d_b.jpg" class="content_image">3)此时B事务还未提交,A在事务内做一次查询,发现查询结果已经改变<img src="https://pic3.zhimg.com/v2-ce2497f31b85344940ad46e9f20a3626_b.jpg" class="content_image">4)B进行事务回滚<img src="https://pic3.zhimg.com/v2-ef013f5c7c697460311c30e5211b4afe_b.jpg" class="content_image">5)A再做一次查询,查询结果又变回去了<img src="https://pic1.zhimg.com/v2-579f1ebaac2616411603687b6a6b6160_b.jpg" class="content_image">由试验得知:在一个进程的事务当中,我更改了其中的一行数据,但是我修改完之后就释放了锁,这时候另一个进程读取了该数据,此时先前的事务是还未提交的,直到我回滚了数据,另一个进程读的数据就变成了无用的或者是错误的数据。我们通常把这种数据叫做脏数据,这种情况读出来的数据叫做賍读。怎么办?依然是靠锁机制。无非是锁的位置不同而已,之前是只要操作完该数据就立马释放掉锁,现在是把释放锁的位置调整到事务提交之后,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作。那么数据库为此种状态的数据库操作规则又给了一个名字叫做:读已提交(Read Committed),或者也可以叫不可重复读。这也就是事务的第二个隔离性。在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……继续看下面的测试结果:1)把隔离性调为READ-COMMITTED(读取提交内容)设置A的事务隔离级别,并进入事务做一次查询<img src="https://pic2.zhimg.com/v2-501201e5e87e1b9b94f5410fc1e60435_b.jpg" class="content_image">2)B开始事务,并对记录进行修改<img src="https://pic1.zhimg.com/v2-bb86e5eaaa89d342125a426c05a096a0_b.jpg" class="content_image">3)A再对user表进行查询,发现记录没有受到影响<img src="https://pic4.zhimg.com/v2-f1ed9b67af09782dc16b832a2796907b_b.jpg" class="content_image">4)B提交事务<img src="https://pic3.zhimg.com/v2-30a832483a152dc31c818de21acf737a_b.jpg" class="content_image">5)A再对user表查询,发现记录被修改<img src="https://pic2.zhimg.com/v2-fd2565c4437cfd385f408e5535694169_b.jpg" class="content_image">试验进行到这里,你会发现,在同一个事务中如果两次读取相同的数据时,最后的结果却不一致。这里我们把这种现象称为:不可重复读。因为在第一个事务读取了数据之后,此时另一个事务把该数据给修改了,这时候事务提交,那么另一个事务在第二次读取的时候,结果就不一样,一个修改前的,一个是修改后的。但是细心的你会发现,既然你说此种隔离性是在事务提交后才释放锁,那么在试验过程中,在该数据未提交前,另一个事务为什么也是仍然可以读取的呀。是我说错了吗?不是的,在这里mysql使用了一个并发版本控制机制,他们把它叫做MVCC,通俗的也就是说:mysql为了提高系统的并发量,在事务未提交前,虽然事务内操作的数据是锁定状态,但是另一个事务仍然可以读取,大多数数据库默认的就是这个级别的隔离性。但mysql不是。而且不只是在更新数据时出现这个问题,在插入数据时仍然会造成类似的这样一种现象:mysql虽然锁住了正在操作的数据行,但它仍然不会阻止另一个事务往表插入新行新的数据。比如:一个事务读取或更新了表里的所有行,接者又有另一个事务往该表里插入一个新行,在事务提交后。原来读取或更改过数据的事务又第二次读取了相同的数据,这时候这个事务中两次读取的结果集行数就不一样。原来更新了所有行,而现在读出来发现竟然还有一行没有更新。这就是所谓的幻读。为了防止同事务中两次读取数据不一致,(包括不可重读和幻读),接下来该如何继续做呢?!mysql依然采取的是MVCC并发版本控制来解决这个问题。具体是:如果事务中存在多次读取同样的数据,MySQL第一次读的时候仍然会保持选择读最新提交事务的数据,当第一次之后,之后再读时,mysql会取第一次读取的数据作为结果。这样就保证了同一个事务多次读取数据时数据的一致性。这时候,mysql把这种解决方案叫做:可重复度(Repeatable-Read),也就是上述所写的第三个隔离性,也是mysql默认的隔离级别。注意:幻读和不可重复读(Read Committed)都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。说到这里,真的就完事了吗?到这里其实mysql并未完全解决数据的一致性问题。只是在读取上做了手脚,解决了传统意义上的幻读和不可重复读。例子:1 A事务开启,B事务开启。 2 B事务往表里面插入了一条数据,但还并未提交。 3 A事务开始查询了,并没有发现B事务这次插入的数据。然后此时B事务提交了数据。 4 于是乎,A事务就以为没有这条数据,就开始添加这条数据,但是却发现,发生了数据 重复冲突。最后这个时候,该我们的最后一种隔离级别也是最高的隔离级:别序列化(serializable)登场了。该隔离级别会自动在锁住你要操作的整个表的数据,如果另一个进程事务想要操作表里的任何数据就需要等待获得锁的进程操作完成释放锁。可避免脏读、不可重复读、幻读的发生。当然性能会下降很多,会导致很多的进程相互排队竞争锁。后记:以上所说的四种隔离性的锁机制应用是数据库自动完成的,不需要人为干预。隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效。








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},但失败了!因为当前数据库中的版本跟查询出来的版本不一致!

猜你喜欢

转载自blog.csdn.net/pyg112358/article/details/88866949