事务 (ACID)
Example: 小明向小强转账10元.
原子性(Atomicity)
转账操作是一个不可分割的操作, 要么成功, 要么失败, 不存在中间状态. 不能说小明转账成功, 小强收款失败.
隔离性(Isolation)
Example2: 小明向小强转账10元.
小明向小红转账10元.
小明转账的两个操作是互不影响的.
一致性(Consistency)
对于上面的转账, 一致性要求每一次转账, 都需要保证整个系统的余额等于所有账户的收入减去所有账户的支出.
如果不遵从原子性, 也就是说如果小明向小强转账10元, 但是只转了一半, 小明账户少了10元, 小强账户并没有增加, 所以没有满足一致性了. 如果不满足隔离性, 也有可能破坏一致性.
所以, 数据库某些操作的原子性和隔离性都是保证一致性的一种手段, 在操作执行完成后保证符合所有既定的约束是一种结果.
我们也可以对表建立约束来保证一致性.
持久性(Durability)
对于转账的交易记录, 需要永久保存.
事务的使用
开始事务 BEGIN [WORK], START TRANSACTION
BEGIN语句代表开启一个事务, 后边的单词WORK可有可无. 开启事务后, 就可以继续执行若干条语句, 这些语句都属于刚刚开启的这个事务的内容.
START TRANSACTION语句和BEGIN语句有着相同的功效, 都标志着开启一个事务.
提交事务 COMMIT
example:
BEGIN;
INSERT INTO student(id, NAME) VALUE(123123, 'xiaoming');
UPDATE student SET NAME='zhangsan' WHERE id=121212;
COMMIT;
自动提交
SHOW VARIABLES LIKE ‘autocommit’ ;
默认情况下,如果我们不显式的使用START TRANSACTION或者BEGIN语句开启一个事务, 那么每一条语句都算是一个独立地事务,这种特性称之为事务的自动提交
如果我们想关闭这种自动提交的功能, 可以使用下边两种方法之一:
- 显式的使用START TRANSACTION或者BEGIN语句开启一个事务. 这样在本次事务提交或者回滚前会暂时关闭自动提交的功能.
- 把系统变量autocommit的值设置为OFF, 就像这样: ‘SET autocommit = OFF;’ 这样的话, 我们写入的多条语句就算是属于同一个事务了, 知道我们显式的写出COMMIT语句来把这个事务提交掉, 或者显式的写出ROLLBACK语句来把这个事务回滚掉.
隐式提交
某些特殊的语句会导致事务提交, 就像是我们输入了COMMIT语句一样, 这些语句包括:
- 定义或修改数据库对象的数据定义语言(DDL). 所谓的数据库对象, 指的就是数据库, 表, 视图, 存储过程等等这些东西. 当我们使用CREATE, ALTER, DROP等语句去修改这些所谓的数据库对象时, 就会隐式的提交前边语句所属于的事务.
- 隐式使用或修改mysql数据库中的表: ALTER USER, CREATE USER, DROP USER, GRANT, RENAME USER, SET PASSWORD 等语句时也会隐式的提交前边所属于的事务.
- 事务控制或关于锁定的语句: 事务还没提交或回滚前使用START TRANSACTION或BEGIN语句开启了另一个事务时, 会隐式的提交上一个事务. autocommit = on. 使用LOCK TABLE, UNLOCK TABLES等关于锁定的语句也会隐式的提交前边所属的事务.
- 加载数据的语句: 例如 LOAD DATA
- 其它特殊语句: ANALYZE TABLE, CACHE INDEX, CHECK TABLE, FLUSH, LOAD INDEX INTO CACHE, OPTIMIZE TABLE, PREPAIR TABLE, RESET等语句也会隐式的提交前边语句所属的事务.
保存点
ROLLBACK 语句使数据库回到事务执行之前的状态. 保存点就是在事务对应的数据库语句中打几个记号点, 我们在调用ROLLBACK语句时可以指定回滚到那个点, 而非回到最初时.
定义保存点: SAVEPOINT 保存点名称 ;
回滚: ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称 ;
删除保存点: RELEASE SAVEPOINT 保存点名称;
隔离性
修改隔离级别: set session transaction isolation level read uncommitted;
查看隔离级别: select @@tx_isolation;
读未提交 (READ UNCOMMITTED)
一个事务可以读取到其它事务还没有提交的数据, 可能出现脏读(读取到无效的数据).
读已提交 (READ COMMITTED)
一个事务只能读到另一个已经提交的事务修改的数据, 并且其它事务每对数据进行一次修改并提交后, 该事务都能查询得到最新值, 会出现不可重复读, 幻读.
幻读:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读.
可重复读 (REPEATABLE READ)
一个事务第一次读过某条记录后, 即使其他事务修改了该记录的值并提交, 该事物之后再读该条记录时, 读到的仍是第一次读到的值, 而不是每次都读到不同的数据, 这就是可重复度, 虽然解决了不可重复读, 但是还是会出现幻读.
串行化 (SERIALIZABLE)
前三种隔离级别允许对同一条记录进行读-读, 读-写, 写-读 的并发操作, 如果我们不允许读-写, 写-读 的并发操作, 可以使用SERIALIZABLE隔离级别, 这种隔离级别因为对同一条记录的操作都是串行的, 所以不会出现脏读, 幻读等现象.
幻读:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。
数据库记录的真实数据除我们自己定义的列的数据以外, 还有一个非必须的隐藏列(row_id)与两个必须的隐藏列:
transaction_id : 6字节, 事务ID, 每次对某条记录进行改动时, 都会把对应的事务id赋值给transaction_id.
roll_pointer : 7字节, 回滚指针, 每次对某条记录进行改动时, 维护一个指针指向修改前的信息.
MVCC (多版本并发控制):
版本链 A(100)==1, B(65)==2
读已提交(假设版本链中的所有事务都为活跃的事务): 事务A读取到的transaction_id=100的行值. 事务B读取到的transaction_id=65的数据.
ReadView
对于读未提交隔离级别的事务来说, 直接读取最新版本就行.
对于串行化隔离级别的事务来说,使用加锁的方式来访问记录.
对于读已提交与可重复读来说, 就需要使用版本链, 判断哪个版本是当前事务可见的.
ReadView的主要内容:
- m_ids: 表示在生成ReadView时当前系统中活跃的读写事务的事务id列表.
- min_trx_id: 表示在生成ReadView时当前系统中活跃的事务中最小的事务id.
- max_trx_id: 表示在生成ReadView时系统中应该分配给下一个事务的id值.
- creator_trx_id: 表示在生成ReadView的事务的id.
事务根据ReadView中维护的内容寻找符合自身条件的数据. 对于可重复读, m_ids在查询之后不会进行更新, 以此保持后面提交的数据不会在下次读取到, 每次读取得同一条数据是不变的.