最近遇到了hive metadata数据库的并发性能问题,厂商给的结论是可以调整mysql的事物隔离级别,达到了预期的效果,就当前事物隔离级别整理了相关资料。
1. 事务隔离级别
事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在MySQL中,事务支持是在引擎层中实现的,MySQL中原生的MyISAM引擎就不支持事务,这也是MyISAM被InnoDB取代的重要原因之一。
以InnoDB为例,剖析MySQL在事务支持方面的特定实现,并基于原理给出相应的建议。
事务要满足的几大特性(ACID:原子性、一致性、隔离性、持久性)
在数据库上有多个事务同时执行的时候,可能出现脏读(dirty read),不可重复读(non-repeatable read), 幻读(phantom read) 的问题。
- 脏读:读到了其他事务还没有提交的数据。
- 不可重复读:对某数据进行读取,发现两次读取的结果不同,也就是说没有读到相同的内容。这是因为有其他事务对这个数据同时进行了修改或删除。
- 幻读:事务 A 根据条件查询得到了 N 条数据,但此时事务 B 更改或者增加了 M 条符合事务 A 查询条件的数据,这样当事务 A 再次进行查询的时候发现会有 N+M 条数据,产生了幻读。
关于幻读的解释:blog.csdn.net/ITWANGBOIT/…
为了解决这些问题,引入了“隔离级别”的概念。
对于隔离级别而言,隔离得越严实,效率就会越低,因此很多时候,需要在二者之间寻找一个平衡,SQL标准的事务隔离级别有:
- 读未提交(read uncommitted)——当一个事务未提交的时候,它做的变更能被别的事务看到;
- 读提交(read committed)——当一个事务提交的时候,它做的变更才会被其他事务看到;
- 可重复读(repeatable read)——一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读的隔离级别下,未提交的变更对其他事务也是不可见的;
- 串行化(serializable)——对于同一行记录,读写都会加锁,当出现读写锁的冲突的时候,后访问的事务必须等待前一个事务执行完成,才能继续执行;
对于以下逻辑:
create table T(c int) engine=InnoDB;
insert into T(c) values(1);
对于read uncommitted的场景:
V1=V2=V3=2,因为事务B,虽然未提交,但是结果已经被A看到了;
对于read committed的场景:
V1=1,V2=V3=2,事务B的更新在提交之后才能被A看到;
对于repeatable read的场景:
V1=V2=1,V3=2,事务在执行期间看到的数据前后必须是一致的;
对于serializable的场景:
在B执行将1改成2的时候,会被锁住,直到事务A被提交的时候,B才得以执行,因此V1=V2=1,V3=2。
2.事务隔离的实现
在事务隔离的实现上,数据库里面会创建一个视图,访问的时候以视图逻辑的结果为准。在可重复读的隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都在使用这个视图。在读提交的隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的,而读未提交的隔离级别下,会直接返回记录上的最新值,没有视图的概念;而串行化的隔离级别下直接以锁的方式来避免并发访问。
在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚的操作,都可以得到前一个状态的值。将一个值从1被改为2、3、4,在回滚日志里,会看到如下记录:
在mysql中,同一条记录在系统中可以存在多个版本,即数据库的多版本并发控制(MVCC),对于read-viewA,要得到1,就必须将当前值依次执行图中所有的回滚操作得到。
回滚日志只有在不需要的时候才会被删除,即系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志才会被删除。即当系统里没有比这个回滚日志更早的read-view的时候。
在Mysql的优化中,建议尽量不要使用长事务,因为长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。
除了对回滚段的影响,长事务还会占用锁资源,也可能会拖垮整个数据库。
3.事务的启动方式
长事务有一些潜在的风险,Mysql的事务启动方式有两种
- 显式启动事务语句,begin或start transaction,配套的语句为commit和回滚语句rollback;
- set autocommit=0,关闭线程的自动提交,当执行一个select语句时,事务就启动了,而且不会自动提交,这个事务持续存在直到主动执行commit或者rollback语句,或者断开连接。
如何查询长事务:
select * from information_schema.innodb_trx
where TIME_TO_SEC(timediff(now(),trx_started)) > 60
参考资料:
time.geekbang.org/column/arti…
time.geekbang.org/column/arti…
www.liaoxuefeng.com/wiki/117776…
blog.csdn.net/qq_34222160…