1、事务
事务,表示一组对数据库的操作,要么全部成功,要么全部失败。在MySQL中,事务支持是在引擎层实现的。经典问题,转账问题。InnoDB支持事务,MyISAM不支持事务。
2、事务控制语句
-- 0. 开启事务
START TRANSACTION;
-- 发现执行没有问题,提交事务
COMMIT;
-- 发现出问题了,回滚事务
ROLLBACK;
3、提交方式
- 自动提交
MySQL就是默认自动提交的, 一条DML(增删改)语句会自动提交一次事务。 - 手动提交:
Oracle 数据库默认手动提交事务;
需要先开启事务START TRANSACTION
,再提交COMMIT
。 - 修改事务的默认提交方式:
查看事务的默认提交方式:SELECT @@autocommit;
-- 1 代表自动提交 ; 0 代表手动提交
修改默认提交方式:set @@autocommit = 0;
4、事务的四大特征【ACID】
- Atomicity原子性:一个事务是不可分割的最小操作单位,要么同时成功,要么同时失败。
- Consistency一致性:数据库总是从一致性的一个状态转移到另一个一致性的状态。
事务操作前后,数据总量不变,数据库的完整性没有被破坏。 - Isolation 隔离性:通常,一个事务所做的修改在最终提交之前,对其他的事务是不可见的。
- Durability持久性:当事务提交或回滚后,数据库会持久化的保存数据。
5、事务的隔离级别
- 概念:多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题。
4.1、存在问题:
- 脏读 dirty read:一个事务,读取到另一个事务中没有提交的数据
- 不可重复读 non-repeatable read:一个事务内,多次读取到的数据不一致
- 幻读 phantom read : 幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。
4.2、隔离级别:
- 注意:
隔离级别从小到大安全性越来越高,但是效率越来越低
- 查询数据库隔离级别:
select @@tx_isolation;
- 数据库设置隔离级别: set global transaction isolation level 级别字符串;
- read uncommitted:读未提交
一个事务还没提交时,它做的变更就能被别的事务看到。
产生的问题:脏读、不可重复读、幻读- read committed:读提交 (Oracle)
一个事务提交之后,它做的变更才会被其他事务看到。
产生的问题:不可重复读、幻读- repeatable read:可重复读 (MySQL默认)
一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
产生的问题:幻读- serializable:串行化
顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
可以解决所有的问题
6、问题演示:
准备一个account表包含字段id、name、balabce
6.1、脏读
事务A读取到了事务B已经修改但是尚未提交的数据,这样不符合一致性。
首先设置隔离级别为 READ UNCOMMITTED: SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
顺序 | 事务 A命令 | balance值 | 事务 B命令 | balance值 |
---|---|---|---|---|
1 | BEGIN; | BEGIN; | ||
2 | SELECT balance FROM account WHERE id = 1; | 10 | ||
3 | UPDATE account SET balance = 0 WHERE id = 1; | |||
4 | SELECT balance FROM account WHERE id = 1; | 0 | ||
5 | SELECT balance FROM account WHERE id = 1; | 0 | ||
8 | COMMIT; | COMMIT; |
6.2、 不可重复读
事务A读取到事务B中已经提交的数据,不符合隔离性
首先设置隔离级别为 READ COMMITTED:SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
注意5、6、7步可以发现在事务B还没有提交的时候,事务 A读到的值没有变化,所以隔离级别READ COMMITTED解决了脏读的问题。但是事务B提交之后,事务 A读取到的值是事务B更新的值,产生了不可重复读问题。
顺序 | 事务 A命令 | balance值 | 事务 B命令 | balance值 |
---|---|---|---|---|
1 | BEGIN; | BEGIN; | ||
2 | SELECT balance FROM account WHERE id = 1; | 10 | ||
3 | UPDATE account SET balance = 0 WHERE id = 1; | |||
4 | SELECT balance FROM account WHERE id = 1; | 0 | ||
5 | SELECT balance FROM account WHERE id = 1; | 10 | ||
6 | COMMIT; | |||
7 | SELECT balance FROM account WHERE id = 1; | 0 | ||
8 | COMMIT; |
6.3、 repeatable read 隔离级别解决了脏读、 不可重复读
首先设置隔离级别为 repeatable read:SET GLOBAL TRANSACTION ISOLATION LEVEL repeatable read;
顺序 | 事务 A命令 | balance值 | 事务 B命令 | balance值 |
---|---|---|---|---|
1 | BEGIN; | BEGIN; | ||
2 | SELECT balance FROM account WHERE id = 1; | 10 | ||
3 | UPDATE account SET balance = 0 WHERE id = 1; | |||
4 | SELECT balance FROM account WHERE id = 1; | 0 | ||
5 | SELECT balance FROM account WHERE id = 1; | 10 | ||
6 | COMMIT; | |||
7 | SELECT balance FROM account WHERE id = 1; | 10 | ||
8 | COMMIT; |
6.4、 repeatable read 隔离级别的幻读问题,间隙锁,MVCC
在repeatable read 隔离级别下
普通的查询语句是一致性读,会根据readview和事务id追溯 版本链
带lock in share mode的查询语句,是当前读,直接读取最新值
对于普通的查询语句,因为在RR隔离级别下,由于MVCC机制,只生成一次readview,不管别的会话是否插入新的数据,普通的查询语句都是看不到的;
对于上锁的查询语句,因为在RR隔离级别下,由于间隙锁的存在,所以查询上锁的行之间的间隙是被锁住的,不允许其他会话 往这个间隙中插入一个记录,如果插入会直接阻塞。