MySQL事务及事务隔离级别 锁机制
什么是事务?
当多个用户访问同一份数据时,一个用户在更改数据的过程中可能有其他用户同时发起更改请求,为保证数据库记录的更新从一个一致性状态更改为另一个一致性状态,这样的操作过程就是事务。事务具有的ACID属性:
-
- 原子性(Atomicity):事务中所有的操作视为一个原子单元,即对于事务所进行的修改、删除等操作只能是全部提交或者全部回滚。
- 一致性(Consistency):事务在完成操作后,必须使所用的数据从一种一致性状态变为另外一种一致性状态,所有的变更都必须应用于事务的修改,以确保数据的完整性。
- 隔离性(Isolation):一个事务中的操作语句所做的修改必须与其他事务相隔离,在进行事务查看数据时数据所处的状态,要么是被另一个事务并发修改之前的状态,要么是修改之后的状态,即当前事务不会查看由另一个并发事务正在修改的数据。这种特性通过锁机制实现。
- 持久性(Durability):事务完成之后,所做的修改对数据的影响是永久的,即使系统重启或者是出现故障数据仍可以恢复。
MySQL中提供多种事务型存储引擎,如InnoDB,BDB。但是MyISAM不支持事务,InnoDB支持事务、行级锁、高并发。
InnoDB事务实现原理:undo日志和redo日志,配合MySQL自身的锁机制
redo日志对应磁盘上MySQL安装目录的ib_logfileN ,
在这个目录下,没有对应的undo日志,当用户rollback一个事务的时候,缓冲区中所有的数据都被放在了表空间对应的.idb数据文件中。
如图这是某个数据库中的 escore 表 在磁盘上对应的表空间文件,当事务回滚时,会将缓冲区的数据放在 escore.idb 中,并在适当的时候刷新到磁盘。
MySQL事务隔离级别
SQL标准定义了4种隔离级别,高级别的隔离级别事务安全,并发性能较低,占用资源较多,低级别反之。
- READ UNCOMMITTED(读未提交内容)
- READ COMMITTED(读已提交内容)
- REPEATABLE READ(可重复读)
- SERIALIZABLE(串行化)
MySQL查看当前隔离级别和设置隔离级别:
-- 查看隔离级别 mysql> show variables like 'tx_isolation'; --设置隔离级别 mysql> set global transaction isolation level READ UNCOMMITTED;
READ UNCOMMITTED(读未提交内容)
在该隔离级别中,所有事务都可以看到其他未提交事务的执行结果,然而其性能也不必其他级别的高很多,因此很少使用,读未提交的数据称为“脏读”。
事务A | 事务B |
mysql> select @@tx_isolation; |
mysql> select @@tx_isolation; |
mysql> begin; mysql> select a from test; |
mysql> begin; mysql> update test set a=a*2 where a=2; |
mysql> select a from test; |
mysql> rollback; |
mysql> select a from test; |
READ COMMITTED(读取提交内容)
一个事务从开始所做的任何改变都是不可见的,事务只能看见已近提交的事务所做的改变,但是一个事务在处理期间可能会发生其他事务修改了数据并提交,那么那么在未提交的事务中就会出现两次读取结果不同的现象,这就是所谓的“不可重复读”。
事务A | 事务B |
mysql> select @@tx_isolation; |
mysql> select @@tx_isolation; |
mysql> begin; mysql> select a from test; |
mysql> begin; mysql> update test set a=a*2 where a=2; |
mysql> select a from test; |
mysql> commit; |
mysql> commit; |
REPEATABLE READ(可重复读)
这是MySQL默认的隔离级别,能确保同一事务的多个实例在并发读取数据时,能看到相同的数据行。但是这种隔离级别也有一个问题:假设一个事务对表中的一行数据进行修改,这种修改会导致整张表的数据都需要更新;同时第二个事务开启,第二个事务是在这张表中插入一行数据。那么,此时就会出现第一个事务发现表中的数据还没有被全部更新的假象,这就是“幻读”。所幸的是在MySQL的InnoDB存储引擎中,通过并发多版本控制的机制已经解决了这个问题。
事务A | 事务B |
mysql> select @@tx_isolation; |
mysql> select @@tx_isolation; |
mysql> begin; mysql> select a from test; |
mysql> begin; mysql> update test set a=0; |
mysql> select a from test; |
mysql> commit; |
mysql> commit; mysql> select a from test; |
(InnoDB存储引擎 MVCC机制:InnoDB通过给每个数据行增加两个隐含值的方式来实现。这两个隐含值记录了数据行的创建时间、以及过期时间、每一行存储事件发生时的系统版本号。每次开启一个新的事物版本号就加一,每个事物都会保存开始时的版本号,每次查询根据事物的版本号来查询数据。)
SERIALIZABLE(串行化)
这是最高的事务隔离级别,通过强制事务排序,使之不可能产生冲突,从而解决了幻读问题。简单来说就是在每个读取的数据行加上共享锁,这个级别上,可能会导致大量的超时现象和所竞争,一般不使用。
InnoDB锁机制
在并发系统中,同一时刻可能有多个客户端在对同一张表做更新操作,为了保证数据的一致性,需要对并发加以控制,因此产生了锁。同时为实现事务隔离级别提供保证。
锁的类型:
1. 共享锁:代号S,Share的缩写。粒度是行或者N行,一个事务获取共享锁后,可以对锁定范围的数据执行读操作。
2. 排他锁:代号X,是eXclusive的缩写,粒度同共享锁,一个事务获得排他锁后,可以对锁定范围执行写操作。
3. 意向锁:是一种表锁,粒度为整张表。分为意向共享锁(IS)和意向排他锁(IX),意向共享锁表示一个事务有意对数据上共享锁或者排它锁,“有意”表示事务想执行操作但是还没有执行。锁与锁之间的关系,要么相容,要么互斥。相容是指:操作同样一组数据时,如果事务A获取了锁a,另一个事务B还可以获取锁b;互斥是指:操作同一组数据时,如果事务A获取了锁a,另一个事务B在事务A释放锁a之前无法获取锁b。
为了提高数据库的并发控制,每次锁的范围越小越好,越小耗费的资源就越多,系统性能又下降,所以为了找到在并发响应和性能的平衡点,“锁粒度应运而生”。
锁粒度
锁的粒度主要分为表锁和行锁,
表锁管理表的开销最小,同时允许的并发量也是最小的锁机制,MyISAM存储引擎使用该锁机制,当要写入数据时,将整个表锁住,此时读、写动作一律等待,同时一些修改表的记录也是表锁。
行锁可以支持最大的并发,InnoDB存储引擎使用该机制,因为采用行级锁,所以可以获得更多性能。
InnoDB 行级锁演示
事务A | 事务B |
mysql> select @@tx_isolation; |
mysql> select @@tx_isolation; |
mysql> begin; mysql> update test set a= 1 where a=0; |
mysql> begin; mysql> update test set a= 2 where a=0; |
mysql> commit; |
mysql> update test set a= 1 where a=0; mysql> commit; |