项目中使用的是mysql数据库,有幸看到《高性能mysql》一书,并且参考其对事务的介绍,结合实际开发对事务进行了测试,主要关注点在事务的隔离级别、innodb_flush_log_at_trx_commit、autocommit参数对mysql性能的影响。
理论
事务就是一组原子性的SQL查询,或者说一个独立的工作单元。那么如果数据库引擎能够成功的执行该组内的所有语句,那么就执行该组语句。如果其中有任何一条语句执行失败,则所有语句都不执行。也就是说,事务内的语句,要么全部执行,要不都不执行。
然后是事务的ACID特征。
原子性:一个事务必须被视作一个不可分割的最小单元。整个事务中的所有操作要么全部执行成功,否则全部回滚,不可能只执行其中一部分,确保事务内的所有语句是一个原子。
一致性:数据库总是从一个一致性的状态到另外一个一致性的状态。如果事务中一条语句执行失败,任何已经执行成功的语句也不会保存到数据库中。
隔离性:通常情况下(这个因事务隔离级别有不同的情况),一个事务在最终提交之前,对其他事务时不可见的,注意是通常情况。
持久性:一旦事务提交,则其所作的修改就会永久保存到数据库中。这个概念我本人还没有很好的理解(比如对与innodb来说,其中的一个参数innodb_flush_log_at_trx_commit,对于事务的提交后数据写入磁盘的时间点有很大的不同。)
一个实现了ACID的数据库,比如对于mysql数据库,通常会需要更强的CPU处理能力、更多的磁盘空间、更大的内存大小。但具体需要什么样的配置,我也没有找出合理的方案,对于我们的期货交易平台,到底需要什么样的配置更合理。
隔离级别(每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些不可见。较低级别的隔离通常可以执行更高的并发,系统的开销个更低):
READ UNCOMMITTED(未提交读):在这个级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据(被称为脏读),这种级别会有很多问题,性能也不会比其他级别好,不推荐使用。
READ COMMITTED(提交读):一个事务开始时,只能看见已经提交的事务所作的修改,换言之,就是只要该事务没有提交,其他事务是无法看见本事务已经做的修改。
REPEATABLE READ(可重复读):该级别保证了同一个事务中多次读取同样记录的结果是一致的(这块我稍后会做出测试,但是有疑问),对于mysql的innodb引擎来说,该引擎使用了MVCC模式进行控制,解决了幻读的问题,但是理论上,其他的引擎如果没有解决幻读的问题时,会出现幻行(对于幻读和幻行,稍后再进行研究)。mysql默认的隔离级别为可重复读。
SERIALIZABLE(可串行化):它强制事务串行执行,避免了幻读的问题。简单来说,该级别时,事务在读取到每一行数据时都会加锁,所以可能导致大量的争锁和超时问题,实际应用下,很少用到。
自动提交(AUTOCOMMIT):mysql默认采用自动提交模式。也就是说,如果不显式的开始一个事务,则每个查询都会被当作一个事务提交。也这样说,如果显示的开始一个事务,就是说使用了start transaction开启事务的时候,事务内的所有查询不再分离成一个个小的事务进行提交操作。
事务日志:事务日志可以帮助提高事务的效率,存储引擎在修改表的数据时只需要修改你内存拷贝,再把修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据持久到磁盘。事务日志采用的是追加的方式,因此写日志的操作时磁盘上一小块区域内的顺序IO,所以采用事务日志的方式相对来说快很多。事务日志持久后,内存中修改的数据在后台可以慢慢的刷回磁盘,修改数据需要写两次磁盘。如果修改的数据已经记录到事务日志并持久化,即使数据本身还没有写回磁盘,操作系统崩溃了,存储引擎在重启时能够自动回复这部分数据。
实践
提交读和可重复读的区别
提交读的隔离级别下,我们进行下面的试验,验证“一个事务从开始知道提交之前,所作的任何修改对其他事务都是不可见的”,先测试insert的情况
事务1 |
事务2 |
mysql> show variables like 'tx_isolation'; +---------------+----------------+ | Variable_name | Value | +---------------+----------------+ | tx_isolation | READ-COMMITTED | +---------------+----------------+ 1 row in set mysql> start transaction; Query OK, 0 rows affected mysql> select * from test; +----+-------+ | id | value | +----+-------+ | 1 | 1 | | 2 | 2 | +----+-------+ 2 rows in set ———————————————— 版权声明:本文为CSDN博主「沉默王二」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qing_gee/article/details/42551179 |
1 |
2 |
mysql> start transaction; Query OK, 0 rows affected mysql> select * from test; +----+-------+ | id | value | +----+-------+ | 1 | 1 | | 2 | 2 | +----+-------+ 2 rows in set mysql> insert into test values(3,3); Query OK, 1 row affected mysql> select * from test; +----+-------+ | id | value | +----+-------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +----+-------+ 3 rows in set ———————————————— 版权声明:本文为CSDN博主「沉默王二」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qing_gee/article/details/42551179 |
mysql> select * from test; +----+-------+ | id | value | +----+-------+ | 1 | 1 | | 2 | 2 | +----+-------+ 2 rows in set |
3 |
4 |
mysql> commit; Query OK, 0 rows affected |
mysql> select * from test; +----+-------+ | id | value | +----+-------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +----+-------+ 3 rows in set mysql> commit; Query OK, 0 rows affected ———————————————— 版权声明:本文为CSDN博主「沉默王二」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qing_gee/article/details/42551179 |
5 |
通过上面的顺序执行,可以看出来“一个事务开始时,只能看到已经提交的事务所作的修改”,并且“一个事务从开始直到提交之前,所作的任何修改对其他事务都是不可见的”,然后在一个事务内“两次执行同样的查询,可能会得到不一样的结果”。
- 隔离级别不变,测试update情况