一.Mysql事物特性
名称 | 定义 |
---|---|
Atomic:原子性 | 一个事务必须视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚 |
Consistency:一致性 | 数据库总数从一个一致性的状态转换到另一个一致性的状态 |
隔离性(isolation) | 一个事务所做的修改在最终提交以前,对其他事务是不可见的 |
持久性(durability) | 一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失 |
二.事物的隔离级别
隔离级别 | 定义 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
读未提交(Read Uncommitted) | 就是说某个事务还没提交的时候,修改的数据,就让别的事务给读到了 | 可能 | 可能 | 可能 |
读已提交Read Committed(不可重复读) | 就是说事务A在跑的时候, 先查询了一个数据是值1,然后过了段时间,事务B把那个数据给修改了一下还提交了,此时事务A再次查询这个数据就成了值2了,这是读了别人家事务提交的数据啊,所以是读已提交。 | 不可能 | 可能 | 可能 |
可重复读(Read Repeatable)默认 | 就是说事务A在执行过程中,对某个数据的值,无论读多少次都是值1;哪怕这个过程中事务B修改了数据的值还提交了,但是事务A读到的还是自己事务开始时这个数据的值 | 不可能 | 不 可能 | 可能 |
可串行化(Serializable ) | 如果要解决幻读,就需要使用串行化级别的隔离级别,所有事务都串行起来,不允许多个事务并行操作 | 不可能 | 不可能 | 不可能 |
- 幻读
比如某个(第一个事物)事务把所有行的某个字段都修改为了2,结果另外一个事务插入了一条数据,那个字段的值是1,然后就尴尬了。第一个事务会突然发现多出来一条数据,那个数据的字段是1。
幻读带来的问题:因为在此隔离级别下,例如:事务1要插入一条数据,我先查询一下有没有相同的数据,但是这时事务2添加了这条数据,这就会导致事务1插入失败,并且它就算再一次查询,也无法查询到与其插入相冲突的数据,同时自身死活都插入不了,这就不是尴尬,而是囧了。
三. MySQL是如何实现Read Repeatable
- MySQL的默认隔离级别是Read Repeatable,就是可重复读,就是说每个事务都会开启一个自己要操作的某个数据的快照(三个事物他们会各自开启自己数据快照),事务期间,读到的都是自己这个数据的快照罢了,对一个数据的多次读都是一样的。
- MySQL是通过MVCC机制来实现Read Repeatable,就是多版本并发控制,multi-version concurrency control
- 当我们使用innodb存储引擎,会在每行数据的最后加两个隐藏列,一个保存行的创建时间,一个保存行的删除时间,但是这儿存放的不是时间,而是事务id,事务id是mysql自己维护的自增的,全局唯一。
- mvcc机制:
id | name | (创建时间)创建事物sid | (删除时间)删除事物did |
---|---|---|---|
1 | 张三 | 120 | 空 |
这是事物 sid=120 insert一条数据到数据库的,
当事物 sid=121时候,查询id=1的这一行的时候,就会查询到主键id=1这条完整的数据.
遵循的规则:创建事务(sid=120)sid <= 当前事务sid(sid=121)
select * from table where id=1,就可以查到上面那一行id=1的所有数据
事务sid=122的事务,将id=1的这一行给删除了,此时就会将id=1的行的删除事务id设置成122
id | name | (创建时间)创建事物sid | (删除时间)删除事物did |
---|---|---|---|
1 | 张三 | 120 | 122 |
2 | 李四 | 119 | 空 |
事务sid=121的事务,再次查询id=1的那一行,能查到吗?
能查到,要求创建事务sid <= 当前事务sid,当前事务sid < 删除事务did
事务id=121的事务,查询id=2的那一行,查到name=李四
事务id=122的事务,将id=2的那一行的name修改成name=小李四
id | name | (创建时间)创建事物sid | (删除时间)删除事物did |
---|---|---|---|
1 | 张三 | 120 | 122 |
2 | 李四 | 119 | 空 |
2 | 小李四 | 122 | 空 |
事务sid=121的事务,查询id=2的那一行,答案是:李四,创建事务id <= 当前事务id,当前事务id < 删除事务id
- 在一个事务内查询的时候,mysql只会查询创建时间的事务id小于等于当前事务id的行,这样可以确保这个行(这一条数据)是在当前事务中创建,或者是之前创建的;
- 同时一个行的删除时间的事务id要么没有定义(就是没删除),要么是必当前事务id大(在事务开启之后才被删除);满足这两个条件的数据都会被查出来。