事务系统
事务介绍
事务:保证原子性、隔离性、一致性、持久性(ACID)的一个或多个数据库操作
只有当事务处于提交或中止的状态,一个事务的生命周期才算结束
开启一个事务,输入以下一条语句后,就可以开始写若干条该事务的语句
-
BEGIN;
-
START TRANSACTION;
- 可加入修饰符
READ ONLY
:只读事务,不允许修改其他事务也能访问的表的数据READ WRITE
WITH CONSISTENT SNAPSHOT
:启动一致性读
- 可同时跟随多个修饰符,比如:
START TRANSACTION READ ONLY, WITH CONSTSTENT SNAPSHOT
- 可加入修饰符
-
提交一个事务:当所有属于该事务的语句写完后提交
COMMIT;
-
手动中止一个事务并回滚:
ROLLBACK
开启事务的几种方式:
-
begin transaction + commit + rollback
这种方式下,该命令并不是一个事务的起点,执行到他们之后第一个操作innodb表的语句,事务才真正启动
read-view是在执行第一个快照读语句时创建的
-
set autocommit = 0 :自动开启事务,commit提交
-
begin transaction + commit work and chain
MySQL只有innodb和NDB支持事务,不支持事务的存储引擎对回滚操作执行无效
ROLLBACK
还可以支持回滚到某个保存点:使用SAVEPOINT XXX
:定义一个保存点XXX,随后便可以使用ROLLBACK TO XXX
回滚到那个保存点,也可以使用RELEASE SAVEPOINT XXX
删除保存点
一些隐式提交操作,当输入这些语句之后会隐式执行COMMIT语句:
- 定义或修改数据库对象(比如创建一个table等等)(即DDL语言)
- 隐式使用或修改mysql数据库中的表:
ALTER USER
,CREATE USER
等等- 事务控制或关于锁定的语句:比如说在一个事务还没有提交或回滚的时候又使用
BEGIN
或START TRANSACTION
会隐式提交上一个事务;或者是LOCK TABLE
之类的加锁解锁- 关于mysql复制的一些语句
ACID特性
原子性、隔离性、一致性、持久性
隔离级别与实现
MySQL在并发执行不同session里的事务时,可能会发生以下四种问题
- 脏写:事务A和事务B修改同一条记录,事务A提交事务后,事务B又回滚,导致事务A提交事务却没有真的修改记录
- 脏读:事务A读一条事务B修改后的记录,读到后,事务B回滚了,导致事务A读到了未提交事务修改的数据
- 不可重复读:事务A读一条正在被事务B修改并提交的记录,每次读到的记录内容都不一样,都是事务B修改的最新值
- 幻读:事务A先根据某些条件查询出一些记录,之后事务B又向表中插入了符合这些条件的记录,事务A再次按照该条件查询时,读出了事务B新插入的记录
隔离级别越高,效率会越低,所以MySQL设置了四种隔离级别,用来权衡隔离性和性能在具体业务中该如何取舍,默认隔离级别是可重复读
- 读未提交:一个事务还没提交时,别的事务就能看见它的更改(直接返回记录上的最新值)
- 读已提交:一个事务提交后,别的事务才能看见更改(每次读时都创建一个read-view)
- 可重复读:一个事务执行过程中看到的数据,总是跟启动时看到的是一样的(在事务启动时创建一个read-view)
- 串行化:对同一行记录,写会加写锁,读会加读锁,出现读写锁冲突时就会发生事务等待(直接加锁避免并行访问)
可以通过SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL xxx;
来设置隔离级别
- GLOBAL:对当前已经存在的session无效,全局影响
- SESSION:对当前会话的后续事务有效,不影响当前已经打开的事务
- 以上都不用:只对当前session下一个将开启的事务有效,那个事务结束后失效
每条记录都一个隐藏列roll_pointer指向自己的undo日志版本链的头节点,其中trx_id记录了该版本对应的事务id,如上图所示,在查询同一条记录的时候,不同时刻启动的事务会有不同的read-view,同一条记录在系统中可以存在多个版本,即MVCC
undo日志会一直保留到系统没有比这个回滚日志更早的read-view的时候删除,所以不建议使用长事务,因为这意味着系统里会存在很古老的read-view,导致大量回滚日志需要保留,占用存储空间
可以在information_schema
库中select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
查询持续时间超过60s的事务
利用锁和mvcc实现并发事务的隔离性
读-读情况:并发事务相继读取相同记录,允许,不需要任何操作
写-写情况:并发事务相继改动相同记录,会发生脏写,任何隔离级别都不允许发生,只能通过加锁解决
- DELETE:做delete的操作 = 在B+树定位该记录位置,获取X锁,执行delete mark,也相当于是一个获取X锁的锁定读
- UPDATE
- 未修改键值 + 更新列占用空间不变 = 在B+树定位位置,获取X锁,执行update,相当于获取X锁的锁定度
- 未修改键值 + 更新列占用空间变化 = 彻底删除该记录, 再插入一条新记录,相当于获取X锁的锁定读 后 由INSERT语句的隐式锁进行保护
- 修改键值 = 对原记录做DELETE 后 INSERT
- INSERT:通过隐式锁保护新插入的记录在本事务提交前不被其他事务访问
读-写情况:可能会出现脏读、不可重复读、幻读,可以选择加锁,或MVCC解决
- 读操作利用MVCC,写操作进行加锁:读的时候读历史版本,写的时候改动记录的最新版本,这种方式下读和写不冲突
- 读、写操作都采用加锁:在一些业务场景不允许读取旧版本记录,所以读-写操作都像写-写操作一样加锁,排队执行