图解数据库事务的隔离级别

 

前言

乐观锁和悲观锁 与 数据库的隔离级别的关系 或者二者使用的场景是什么?mysql

我在网上所能找到的答案,帮助我的的理解。sql

答案一:事务隔离级别是并发控制的总体解决方案,其其实是综合利用各类类型的锁和行版本控制,来解决并发问题。锁是数据库并发控制的内部机制,是基础。对用户来讲,只有当事务隔离级别没法解决一些并发问题和需求时,才有必要在语句中手动设置锁。数据库

那么事务隔离级别没法解决哪些并发问题呢? 安全

先来看看事务的隔离级别: 并发

事务隔离级别

为了解决多个事务并发会引起的问题,进行并发控制。数据库系统提供了四种事务隔离级别供用户选择。编辑器

  • Read Uncommitted 读未提交:不容许第一类更新丢失。容许脏读,不隔离事务。高并发

  • Read Committed 读已提交:不容许脏读,容许不可重复读。性能

  • Repeatable Read 可重复读:不容许不可重复读。但可能出现幻读。ui

  • Serializable 串行化:全部的增删改查串行执行。atom

读未提交

事务读不阻塞其余事务读和写,事务写阻塞其余事务写但不阻塞读。 能够经过写操做加“持续-X锁”实现。

 

 

读已提交

事务读不会阻塞其余事务读和写,事务写会阻塞其余事务读和写。 能够经过写操做加“持续-X”锁读操做加“临时-S锁”实现

 

 

可重复读

事务读会阻塞其余事务事务写但不阻塞读,事务写会阻塞其余事务读和写。 能够经过写操做加“持续-X”锁,读操做加“持续-S锁”实现。

 

 

串行化

“行级锁”作不到,需使用“表级锁”。

可串行化

若是一个并行调度的结果等价于某一个串行调度的结果,那么这个并行调度是可串行化的。

 

 

区分事务隔离级别是为了解决脏读、不可重复读和幻读三个问题的。

事务隔离级别 回滚覆盖 脏读 不可重复读 提交覆盖 幻读
读未提交 x 可能发生 可能发生 可能发生 可能发生
读已提交 x x 可能发生 可能发生 可能发生
可重复读 x x x x 可能发生
串行化 x x x x x

既然事务的隔离级别能够作到这些。还须要悲观锁干什么呢?

个人理解是:(理解有错误的,请你们指正)

Mysql默认使用的隔离级别是:可重复读

MSSQL默认使用的隔离级别是:读已提交

若是在MSSQL使用默认使用的隔离级别时读已提交的同事也想开发过程当中想解决:不可重复读,提交覆盖和幻读等问题就可使用悲观锁实现。

MYSQL同理。

尽管悲观锁可以防止丢失更新和不可重复读这类问题,可是它很是影响并发性能,所以应该谨慎使用。

乐观锁不能解决脏读的问题,所以仍须要数据库至少启用“读已提交”的事务隔离级别

经常使用的解决方案

2.悲观锁和共享锁、排它锁有是什么关系呢?

共享锁和排它锁是悲观锁的不一样的实现,它俩都属于悲观锁的范畴。即悲观锁由共享锁和排它锁来实现的。

从读写角度,分共享锁(S锁,Shared Lock)和排他锁(X锁,Exclusive Lock),也叫读锁(Read Lock)和写锁(Write Lock)。理解:

持有S锁的事务只读不可写。若是事务A对数据D加上S锁后,其它事务只能对D加上S锁而不能加X锁。 ​ 持有X锁的事务可读可写。若是事务A对数据D加上X锁后,其它事务不能再对D加锁,直到A对D的锁解除

注:要使用悲观锁,咱们必须关闭mysql数据库的自动提交属性,由于MySQL默认使用autocommit模式,也就是说,当你执行一个更新操做后,MySQL会马上将结果进行提交。咱们可使用命令设置MySQL为非autocommit模式:set autocommit=0;设置完autocommit后,咱们就能够执行咱们的正常业务了。

开始事务使用begin;/begin work;/start transaction; (三者选一就能够);

提交事务使用commit;/commit work;

两种锁的具体实现以下:

  • 共享锁:悲观锁都是由数据库实现的,那共享锁在mysql中是经过什么命令来调用呢。经过在执行语句后面加上lock in share mode 就表明对某些资源加上共享锁了。

好比,我这里经过mysql打开两个查询编辑器,在其中开启一个事务,并不执行commit语句。city表DDL以下

  1. CREATE TABLE `city` (

    `id` bigint(20) NOT NULL AUTO_INCREMENT,

    `name` varchar(255) DEFAULT NULL,

    `state` varchar(255) DEFAULT NULL,

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;

     

img

begin;


SELECT * from city where id = "1" lock in share mode;

而后在另外一个查询窗口中,对id为1的数据进行更新

img

update city set name="666" where id ="1";

此时,操做界面进入了卡顿状态,过几秒后,也提示错误信息

[SQL]update city set name="666" where id ="1"; [Err] 1205 - Lock wait timeout exceeded; try restarting transaction

那么证实,对于id=1的记录加锁成功了。在上一条记录尚未commit以前,这条id=1的记录被锁住了,只有在上一个事务释放掉锁后才能进行操做,或用共享锁才能对此数据进行操做。

若是在上面一条记录加上commit;

begin;


SELECT * from city where id = "1" lock in share mode;


commit;

则 update city set name="666" where id ="1";能够执行成功!

再实验一下:

img

update city set name="666" where id ="1" lock in share mode;

[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1

加上共享锁后,也提示错误信息了,经过查询资料才知道,对于update,insert,delete语句会自动加排它锁的缘由

因而,我又试了试SELECT * from city where id = "1" lock in share mode;

img

这下成功了。

  • 排他锁。排它锁与共享锁相对应,就是指对于多个不一样的事务,对同一个资源只能有一把锁。

与共享锁类型,在须要执行的语句后面加上for update就能够了。

排他锁的实现以下:

  • 使用场景举例:以MySQL InnoDB为例

商品t_items表中有一个字段status,status为1表明商品未被下单,status为2表明商品已经被下单(此时该商品没法再次下单),那么咱们对某个商品下单时必须确保该商品status为1。假设商品的id为1。 若是不采用锁,那么操做方法以下:

//1.查询出商品信息
select status from t_items where id=1;

//2.根据商品信息生成订单,并插入订单表 t_orders
insert into t_orders (id,goods_id) values (null,1)

//3.修改商品status为2
update t_items set status=2;

可是上面这种场景在高并发访问的状况下极可能会出现问题。例如当第一步操做中,查询出来的商品status为1。可是当咱们执行第三步Update操做的时候,有可能出现其余人先一步对商品下单把t_items中的status修改成2了,可是咱们并不知道数据已经被修改了,这样就可能形成同一个商品被下单2次,使得数据不一致。因此说这种方式是不安全的。

  • 使用悲观锁来解决问题

在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当咱们在查询出t_items信息后就把当前的数据锁定,直到咱们修改完毕后再解锁。那么在这个过程当中,由于t_items被锁定了,就不会出现有第三者来对其进行修改了。须要注意的是,要使用悲观锁,咱们必须关闭mysql数据库的自动提交属性,由于MySQL默认使用autocommit模式,也就是说,当你执行一个更新操做后,MySQL会马上将结果进行提交。咱们可使用命令设置MySQL为非autocommit模式:set autocommit=0; 设置完autocommit后,咱们就能够执行咱们的正常业务了。具体以下:

//0.开始事务

begin;/begin work;/start transaction; (三者选一就能够)

//1.查询出商品信息

select status from t_items where id=1 for update;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status为2

update t_items set status=2;

//4.提交事务

commit;/commit work;

上面的begin/commit为事务的开始和结束,由于在前一步咱们关闭了mysql的autocommit,因此须要手动控制事务的提交。 上面的第一步咱们执行了一次查询操做:select status from t_items where id=1 for update;与普通查询不同的是,咱们使用了select…for update的方式,这样就经过数据库实现了悲观锁。此时在t_items表中,id为1的那条数据就被咱们锁定了,其它的事务必须等本次事务提交以后才能执行。这样咱们能够保证当前的数据不会被其它事务修改(其余事务不能读也不能修改当前的数据)。须要注意的是,在事务中,只有SELECT ... FOR UPDATELOCK IN SHARE MODE 操做同一个数据时才会等待其它事务结束后才执行,通常SELECT ... 则不受此影响。拿上面的实例来讲,当我执行select status from t_items where id=1 for update;后。我在另外的事务中若是再次执行select status from t_items where id=1 for update;则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,可是若是我是在第二个事务中执行select status from t_items where id=1;则能正常查询出数据,不会受第一个事务的影响。

  • Row Lock与Table Lock

使用select…for update会把数据给锁住,不过咱们须要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,因此只有「明确」地指定主键或者索引,MySQL 才会执行Row lock (只锁住被选取的数据) ,不然MySQL 将会执行Table Lock (将整个数据表单给锁住。举例以下: 一、select * from t_items where id=1 for update; 这条语句明确指定主键(id=1),而且有此数据(id=1的数据存在),则采用row lock。只锁定当前这条数据。 二、select * from t_items where id=3 for update; 这条语句明确指定主键,可是却查无此数据,此时不会产生lock(没有元数据,又去lock谁呢?)。 三、select * from t_items where name='手机' for update; 这条语句没有指定数据的主键,那么此时产生table lock,即在当前事务提交前整张数据表的全部字段将没法被查询。 四、select * from t_items where id>0 for update; 或者select * from t_items where id<>1 for update;(注:<>在SQL中表示不等于) 上述两条语句的主键都不明确,也会产生table lock。 五、select * from t_items where status=1 for update;(假设为status字段添加了索引) 这条语句明确指定了索引,而且有此数据,则产生row lock。 六、select * from t_items where status=3 for update;(假设为status字段添加了索引) 这条语句明确指定索引,可是根据索引查无此数据,也就不会产生lock。

参考的连接有:https://www.jianshu.com/p/71a79d838443

http://www.javashuo.com/article/p-guxaysjl-dv.html

http://www.javashuo.com/article/p-riaxoywc-gp.html

https://blog.csdn.net/yinni11/article/details/81238541

感谢这四位博主作的贡献!

相关文章
相关标签/搜索