事务理解
事务组成
- 简单的来说事务可以由一条简单的sql语句组成,也可以由一组复杂的sql语句组成(事务是一个程序逻辑单元)
事务特征
在数据库针对事务进行提交的时候,要么是所有的修改都保存,要么所有的修改都丢弃 (原子性,要么整个事务全部完成,要么整个事务全部丢弃)
事务是访问并更新数据库各种数据项的一个程序执行单元
mysql的innodb 引擎是支持事务的, myisam 是不支持事务的, innodb中每一条sql语句都是事务;我们如果想要取消自动提交事务,可以通过设置 set autocommit = 0; 设置当前会话手动提交事务
常见事务的控制语句
-- 显示的开启事务 start transaction | begin; -- 提交事务,并且对于当前数据库所作的操作修改做持久化 commit; -- 回滚事务,结束用户的事务,并撤销正在进行的所有未提交的修改 rollback; -- 创建一个保存点,一个事务可以有多个保存点 savepoint identifier -- 删除一个保存点 release savepoint identifier -- 事务回滚到保存点 rollback to [savepoint] identifier
事务的ACID特征
原子性 (可借助多线程对全局数据做写入操作哪里的理解) 绑定操作单元
浅层理解:这个好理解, 就是说事务一定是一个原子操作序列单元,所有操作在一次执行过程中仅仅只会允许两种状态 --- 要么全部执行成功 要么全部执行失败 (绑定在一个事务中的所有操作)
深入理解:事务操作要么都做(提交),要么都不做(回滚);事务是访问并更新数据库各种数据项的一个程 序执行单元,是不可分割的工作单位;通过undolog来实现回滚操作。undolog记录的是事务每步具体操作,当回滚时,回放事务具体操作的逆运算;(回滚原理,沿着undolog往回恢复操作之前的样子)
隔离性
一个事务的执行不能被其他事务干扰。
要解释隔离性: 首先理解事务并发,我们的数据库的用户是很多的,是可以并发执行事务的,隔离性指的就是并发的事务是相互隔离,互不干扰的。 也就是说不同的事务并发操作相同的数据时候,每一个事务都是存在自己独立完整的数据空间的
深入理解原理:事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,也就是事务提交前对其他事务都不可见;通过 MVCC 和 锁来实现;MVCC 时多版本并发控制,主要解决一致性非锁定读,通过记录和获取行版本,而不是使用锁来限制读操作,从而实现高效并发读性能。锁用来处理 并发 DML 操作;数据库中提供粒度锁的策略,针对表(聚集索引B+树)、页(聚集索引B+树叶 子节点)、行(叶子节点当中某一段记录行)三种粒度加锁
MVCC性能更优, 避免了加行锁读操作,避免了锁等待
持久性 (先写日志再写磁盘, 高效)
事务一旦提交,他对数据的改动是持久性的,事务一旦提交,相关的数据就应该从游离态或瞬时态变成持久态。
redo log是InnoDB引擎特有的日志模块,记录的是:在某个数据页上做了什么修改;
当MySQL执行一条更新语句的时候,InnoDB引擎会把记录先写到redo log文件中,并更新内存。此时这条更新操作就算完成了,但是并没有将数据的更新持久化的磁盘,InnoDB引擎会在一个合适的时机将数据的更新操作持久化到磁盘。
事务提交后,事务DML操作将会持久化(写入redolog磁盘文件 哪一个页 页偏移值 具体数据); 即使发生宕机等故障,数据库也能将数据恢复。redo log记录的是物理日志; (意思就是说事务的所有的DML操作都会写入redo log 磁盘文件, 将操作的位置和具体操作都记录在redo log日志中, 所以就算是发生了宕机等现象也是可以还原的)
一致性 (是一种状态, 无论是回滚还是提交, 数据完整性不被破坏)
指的是数据库操作前后需要保持数据的完整性和一致性, 事务的执行是导致数据库从一种一致性状态转换为另外一种一致性状态,也就是说事务执行前后,数据库的完整性约束没有被破坏. (eg : A账户向B账户转账500元,不可能说A账户减少500元,B账户却没有增加500元)
一致性由原子性、隔离性以 及持久性共同来维护的。
事务的并发异常
脏读
- 事务A可以读取到另外一个事务B还没有提交的数据,这个事务A读取到的未提交数据就是所谓的脏读数据,要演示读取到脏读数据, 首先我们需要将隔离等级设置为 read uncommitted 读未提交
- 演示脏读
不可重复读 (读 + 读,结果不一样)
事务(A) 可以读到另外一个事务(B)中提交的数据;通常发生在一个事务中两次读到的数据是不一样的情况; 不可重复读在隔离级别 READ COMMITTED 存在。一般而言,不可重复读的问题是可以接受的,因为读到已经提交的数据,一般不会带来很大的问题,所以很多厂商(如Oracle、 SQL Server)默认隔离级别就是READ COMMITTED
A 事务 第一次读 , B事务还没有commit , A 事务第二次读, B事务进行了commit, 两次读结果就不会一致
幻读 (读 + 写, 以为自己读到了,去对其进行写操作,实际不存在)
事务中一次读操作不能支撑接下来的业务逻辑;通常发生在一个事务中一次读判断接下来写操作失败的情况; 例如:以name为唯一键的表,一个事务中查询 select * from t where name = 'mark'; 不存在,接下来 insert into t(name) values ('mark'); 出现错误,此时另外一个 事务也执行了 insert 操作;幻读在隔离级别 repeatable read 及以下存在;但是可以在 repeatable read 级别下通过读加锁(使用共享锁)解决;
简单的举出一个幻读的例子,就是A事务刚刚查询一个记录不存在,接下来准备做insert操作,这个时候另外的B事务插入了这个记录,并提交,此时相当于是刚刚的记录查询是虚幻的,不是真实的,也称为幻读
解决办法1: 查询操作的时候加上一把共享读锁, 读锁,间隙锁,锁住之后别的事务就不能对齐进行插入操作了, 查询的时候加上共享锁,事务B 就无法完成插入操作, 这样就避免了幻读现象了 (mysql中的共享锁就相当于我们学操作系统时候的写锁)
解决办法2: 直接修改事务的隔离级别为顺序读: serializable, 串行之后会读操作也会自动加上共享锁 share in mode,但是基本不提倡,效率太低下了,锁等待,性能下降的可怕.
事务隔离级别
事务隔离等级越高,会越来越安全, 但是效率会变得越来越低,并发性也越来越差
默认等级一般是读已提交或者是可重复读
ISO和ANIS SQL标准制定了四种事务隔离级别的标准,各数据库厂商在正确性和性能之间做了妥 协,并没有严格遵循这些标准;MySQL innodb默认支持的隔离级别是 REPEATABLE READ;
隔离级别的底层实现 (mvcc + 各种锁) mvcc + 各种锁都是为了保证在并发执行下事务之间的隔离性, 原子性 等性质
READ UNCOMMITTED 读未提交;该级别下读不加锁,写加排他锁,写锁在事务提交或回滚后释放锁 (读未提交,这个级别下读是不会加锁的,也没有mvcc)
READ COMMITTED 读已提交; 该级别下读不会自动加锁, 而是采取的mvcc(多版本并发控制), 也就是提供一致性非锁定读, 此时读取操作 读取历史快照数据;该隔离级别下读取历史版本的最新数据,所以读取的是已提交的数据; (正是因为读的是历史版本最新的数据, 也就是当前已提交的数据, 所以不支持重复读,因为有其他事务做新的提交, 该事务重复再读取结果会和提交前不一致)
REPEATABLE READ 可重复读;该级别下也支持 MVCC,此时读取操作读取事务开始时的版本数据
也就是说 repeatable read 如何支持可重复读, 不管你提交多少新的版本, 我永远只是读取开启事务的第一版, 也就是我最初的一版历史数据, 其他事务提交的最新版历史快照数据我不会理睬. ( 因为一直读取第一版数据, 自然不会因为最新版数据引起重复读数据不一致的问题, 这个就是 reapeatable read, 所以现在懂了塞,为啥它可以支持重复读数据一致 )
SERIALIZABLE 可串行化;该级别下给读加了共享锁 (S锁);所以事务都是串行化的执行;此时隔离级别最严苛;
小总结
首先本章主要学习事务的定义,了解事务的重要性质, 初步认识锁 + mvcc 支持并发多事务执行操做的原因 (以及认识各种隔离级别的底层实现)
啥叫事务:一条简单的sql语句 或者 是 多条复杂的sql语句的集合
事务需要保证哪些性质 :
原子性: undo log 回滚操作保证所有的sql语句要么全部执行成功,要么全部执行失败
隔离性: mvcc + 锁 保证 读写操作 各个事务之间相互隔离,互不干扰, 写操作肯定要加x锁, 读操作 分版本,
read uncommitted 不会加锁也没有mvcc
read committed 存在mvcc多版本并发控制, 可能出现幻读现象,可以s共享锁避免, 读最新版本数据,存在不可重复读问题,
repeatable read 读取最老版本的数据 避免重复读数据不一致的问题, 但是也可能出现幻读, 解决办法还是加s共享锁
serializable: 顺序读,读操作自动s锁, 绝对安全,但是并发性是最差的
持久化:使用redo log 支持持久化, 对于mysql数据的修改物理地址 + 如何修改做日志记录, 就算是宕机了也可以利用 redo log 日志进行恢复
一致性 : 保证事务前后数据的完整性,从一种一致性状态转换为另外一种一致性状态, 一致性的保证依赖的是上述三种性质
read committed 读最新版本数据会出现 不可重复读的问题 (读取的数据是其他事务提交前和提交后的不同结果)
repeatable read : 可重复读,读历史快照数据的第一版数据,避免重复读结果不一致,但是可能出现幻读的现象,幻读,读 + 写 读完根据读结果进行 update 操作。问题,刚读完,其他事务进行了update + 提交。此刻之前select的结果已然是不正确了,之前读的数据就是幻读数据了 (如何避免,加上 s锁)
留个后文,mysql各种锁的详解,锁----》 事务至关重要