超详细SQL事务——基于锁的理解

一、事务的特性

1.原子性

原子性是指事务是一个不可分割的整体,事务里面的操作要么全部成功,要么全部失败。

2.一致性

事务一致性的概念是:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。

3.隔离性

事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

4.持久性

持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

二、 事务隔离性详解(隔离级别)

多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。

事务隔离性的语句设置:
MySQL数据库共定义了四种隔离级别:
  1. Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。
  2. Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
  3. Read committed(读已提交):可避免脏读情况发生。
  4. Read uncommitted(读未提交):最低级别,以上情况均无法保证。

mysql数据库查询当前事务隔离级别:select @@tx_isolation

mysql数据库默认的事务隔离级别:Repeatable read(可重复读)

mysql数据库设置事务隔离级别:set transaction isolation level 隔离级别名

如果事务不考虑隔离性,可能会引发以下问题:

  1. 脏读
    脏读指一个事务读取了另外一个事务未提交的数据。

  2. 不可重复读
    不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。
    不可重复读和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。

  3. 幻读
    虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

三、 如何保证事务的原子性和一致性

redo和undo机制是数据库实现事务的基础。redo日志用来在断电/数据库崩溃等状况发生时重演一次刷数据的过程,把redo日志里的数据刷到数据库里,保证了事务的持久性(Durability); undo日志 是在事务执 行失败的时候撤销对数据库的操作,保证了事务的原子性(Atomicity)。

·
·
·

数据库使用锁的方式保证隔离性(也就是最终数据保证了一致性)

InnoDB实现的方式有以下两种:

悲观锁控制
乐观锁(数据多版本)控制

  1. 悲观锁:拿来数据就先上锁,这样别的线程拿数据就会阻塞。像行锁,页锁,表锁以及共享锁,排它锁都是悲观锁。我们说的那些锁一般都是悲观锁。

  2. 乐观锁:悲观锁只是一种概念,其实没有锁,MySql默认其他线程不会改变数据,所以不加锁,只是在提交的时候判断是不是被更改过了,一般用版本戳(MVCC )来实现乐观锁。

一,使用(悲观)锁控制

   按照锁的使用方式分为共享锁和排它锁。

   1. 共享锁(Share Locks,S锁):一个线程给数据加上共享锁后,其他线程只能读取数据,不能修改。

   2. 排它锁(eXclusive Locks,X锁):一个线程给数据加上排它锁后,其他线程不能读取也不能修改。

   3. 没有锁:InnoDB所有的普通select都是快照读,都不加锁。

兼容性规则为:共享锁和共享锁兼容,排它锁和其他的锁都排斥。

如果一个请求的锁模式与当前的锁兼容,InnoDB就将请求授予该事务,反之,如果两者不兼容,该事物就会等到锁释放。

对于update,delete和insert语句,InnoDB会自动给涉及的数据加排它锁,所以如果插入操作卡住了也无法查询;

对于普通的select语句,InnoDB不会加任何锁;

事务也可以通过以下语句手动的给事务加共享锁和排它锁:

select * from table_name where  ... lock in share mode 会给事务加上共享锁;

select * from table_name where ... for update 会给事务加上排它锁。
按照锁的粒度分为行锁和表锁以及间隙锁

行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件来检索数据才会用到行锁,否则InnoDB将会使用表锁。

  表锁:select * from table_nane where name = ‘小巷’ for update 。name字段不是唯一索引字段,所以是表锁。

  行锁:select * from table_name where id = 1 for update 。id 字段为唯一索引字段,所以使用的就是行锁,且是排它锁。

  页锁(又叫Gap锁):又称为间隙锁。所谓表锁锁表,行锁锁行,那么页锁折中,锁相邻的一组数据,可以用来防止幻读。

通过加锁控制,可以保证数据的一致性,但是同样一条数据,不论用什么样的锁,只可以并发读,并不可以读写并发(因为写的时候加的是排他锁所以不可以读),这时就要引入数据多版本控制了,也就是乐观锁这个概念。

上面说到的这些锁都算作是悲观锁的概念。

不同隔离级别下的加锁情况:

RRRC(只有行锁)
主键行锁,间隙锁(范围)行锁
唯一索引行锁,间隙锁(范围)行锁
非唯一索引行锁,间隙锁(范围)行锁
非索引表锁行锁
二,使用数据多版本(MVCC)控制

使用锁控制并发时,只要是写数据的任务没有完成,数据就不可以被其他的任务获取,就连读数据的select操作也会阻塞,这对并发度要求较大的环境有很大的影响,为了解决这个问题引出了数据多版本。

数据多版本实现的原理是:

1,写任务发生时,首先复制一份旧数据,以版本号区分

2,写任务操作新克隆的数据,直至提交

3,并发读的任务可以继续从旧数据(快照)读取数据,不至于堵塞

排它锁 是 串行执行
共享锁 是 读读并发
数据多版本 是 读写并发

三,redo,undo,回滚段

在InnoDB的具体实现上,依赖redo日志,undo日志,回滚段(rollback segment)

redo日志的作用?

数据库事务提交后,按照随机方式写入磁盘上性能太低,为了提高效率先写到redo日志里,再刷到磁盘上(此时变成了顺序写)。

假如我在提交事务时数据库崩溃了,重启时,会从redo日志里把没有刷到磁盘的数据刷到磁盘上(redo意为把刷磁盘的操作进行一次重演)。简言之,redo的作用是为了保障已提交事务的ACID特性,也就是保证了数据的D(Durability)持久性。

undo日志的作用?

数据库修改数据但未提交时,会将事务修改数据的镜像(即旧数据)存到undo日志里,当事务回滚或数据库崩溃时,会从undo日志里获取旧数据,避免未提交数据对数据库的影响。简言之,undo的作用是为了保障未提交的事务不会对数据库的ACID产生影响。也就是保证了操作的A(Atomicity)原子性。

回滚段的作用?

存储undo日志的地方,就是回滚段。

好文章:https://draveness.me/mysql-innodb.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值