一、事务的特性
1.原子性
原子性是指事务是一个不可分割的整体,事务里面的操作要么全部成功,要么全部失败。
2.一致性
事务一致性的概念是:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。
3.隔离性
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
4.持久性
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
二、 事务隔离性详解(隔离级别)
多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。
事务隔离性的语句设置:
MySQL数据库共定义了四种隔离级别:
- Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。
- Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
- Read committed(读已提交):可避免脏读情况发生。
- Read uncommitted(读未提交):最低级别,以上情况均无法保证。
mysql数据库查询当前事务隔离级别:select @@tx_isolation
mysql数据库默认的事务隔离级别:Repeatable read(可重复读)
mysql数据库设置事务隔离级别:set transaction isolation level 隔离级别名
如果事务不考虑隔离性,可能会引发以下问题:
-
脏读
脏读指一个事务读取了另外一个事务未提交的数据。 -
不可重复读
不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。
不可重复读和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。 -
幻读
虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
三、 如何保证事务的原子性和一致性
redo和undo机制是数据库实现事务的基础。redo日志用来在断电/数据库崩溃等状况发生时重演一次刷数据的过程,把redo日志里的数据刷到数据库里,保证了事务的持久性(Durability); undo日志 是在事务执 行失败的时候撤销对数据库的操作,保证了事务的原子性(Atomicity)。
·
·
·
数据库使用锁的方式保证隔离性(也就是最终数据保证了一致性)
InnoDB实现的方式有以下两种:
悲观锁控制
乐观锁(数据多版本)控制
-
悲观锁:拿来数据就先上锁,这样别的线程拿数据就会阻塞。像行锁,页锁,表锁以及共享锁,排它锁都是悲观锁。我们说的那些锁一般都是悲观锁。
-
乐观锁:悲观锁只是一种概念,其实没有锁,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锁):又称为间隙锁。所谓表锁锁表,行锁锁行,那么页锁折中,锁相邻的一组数据,可以用来防止幻读。
通过加锁控制,可以保证数据的一致性,但是同样一条数据,不论用什么样的锁,只可以并发读,并不可以读写并发(因为写的时候加的是排他锁所以不可以读),这时就要引入数据多版本控制了,也就是乐观锁这个概念。
上面说到的这些锁都算作是悲观锁的概念。
不同隔离级别下的加锁情况:
RR | RC(只有行锁) | |
---|---|---|
主键 | 行锁,间隙锁(范围) | 行锁 |
唯一索引 | 行锁,间隙锁(范围) | 行锁 |
非唯一索引 | 行锁,间隙锁(范围) | 行锁 |
非索引 | 表锁 | 行锁 |
二,使用数据多版本(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