事务: 一组操作要不成功,要不失败。在MySQL中,事务支持是在引擎层实现的,但并不是所有的引擎都是支持事务的,例如MySQL的原生引擎MyISAM,这也是它被InnoDB引擎取代的重要原因。
一提到事务,就必然想到事务的特性:ACID→(原子性,一致性,隔离性,持久性)。
当数据库上有多个事务同时执行时,就可能出现脏读,不可重复读,幻读的问题,为了解决该问题,便有了“隔离级别”的概念。
脏读:当前事务读到其他事务未提交的数据。
不可重复读:在同一个事务内,前后读取记录内容不一致。
幻读:在同一个事务内,前后两次读取的记录集合不一致。
这里使用Java的线程概念能更好地理解!
SQL的事务隔离级别包括:
- 读未提交:一个事务还没提交,它做的变更能被别的事务看见。
- 读提交:一个事务提交之后, 它做的变更才能被其他事务看到。
- 可重复读: 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的,事务启动时会创建一个“静态视图”。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
- 串行化: 对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
我们可以结合下面的图标来解释上各个隔离级别:
- 若隔离级别是“读未提交”, 则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。
- 若隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。
- 若隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,重点:事务在执行期间看到的数据前后必须是一致的。
- 若隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住,因为 事务A一开始查询值1的时候就获得了读锁。直到事务 A 提交后,读锁才被释放,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。
隔离级别在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。
- 在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
- 在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。
- 在“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;
- 在“串行化”隔离级别下直接用加锁的方式来避免并行访问。
Oracle数据库的默认级别为“读提交”。
配置的方式是,将启动参数 transaction-isolation 的值设置成 READ-COMMITTED。你可以用 show variables 来查看当前的值。
事务隔离的实现:这里主要拿“可重复读”说事, 在 MySQL 中,每条记录在更新时都会生成一条回滚操作记录,记录上记录着之前的数据,通过回滚操作,可以得到前一个状态的值,同一条记录在系统中可以存在多个版本,这是数据库的多版本并发控制(MVCC)。
当系统判断没有事务需要用到当前的回滚日志时,回滚日志便会被删除。
数据库中有长事务的概念,使用长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间,同时也可能会长期占用锁资源,从而拖垮整个数据库。
MySQL 的事务启动方式有以下几种:
- 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。
- set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。
我会建议你总是使用 set autocommit=1(自动提交事务), 通过显式语句的方式来启动事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务 。
练习问题:
- 事务的概念是什么?
- mysql的事务隔离级别读未提交, 读已提交, 可重复读, 串行各是什么意思?
- 读已提交, 可重复读是怎么通过视图构建实现的?
- 可重复读的使用场景举例? 对账的时候应该很有用?
- 事务隔离是怎么通过read-view(读视图)实现的?
- 并发版本控制(MCVV)的概念是什么, 是怎么实现的?
- 使用长事务的弊病? 为什么使用常事务可能拖垮整个库?
- 事务的启动方式有哪几种?
- commit work and chain的语法是做什么用的?
- 怎么查询各个表中的长事务?
- 如何避免长事务的出现?