面试官:说说MySQL的事务隔离?
点击上方「蓝字」关注我们
数据库的事务隔离
前段时间,公司内部遇到了一个问题,就是我们创建的同一批任务,别分配给了不同的实例去执行,导致线上的结果出现问题。 另一个组的leader说没有开启事务,设置下事务就可以。 数据库一出现一致性问题,就说开启事务,我就有点疑惑,数据库的事务到底是怎么保证一致性的。
在看下面的内容,我们可以先思考几个问题。
❝数据库的隔离级别都有什么? 数据库的MVVC视图是怎么实现的? 数据库的隔离级别是为了解决什么问题的? 看完上面三个问题,自己能回答上来几个呢?不急。我们继续往下看
❞
数据库的事务
数据库的事务我们简单来说就是用来保证数据的正确性,它只有两个操作:事务要么成功,要么失败并进行回滚。
为什么这么做呢?这是因为一般我们进行事务操作,都会进行一组操作。比如你常见的金融转账。
在这个转账事务里面包含2个操作:
扣自己银行账户的钱 给对应的账户添加收到的钱。
现在思考下,如果我们没有添加事务,那么会出现什么样的情况呢?
如果先扣钱成功,执行给别人加钱失败。而钱已经扣了,对方没收到钱,你说怎么办? 如果先给对方加钱,而扣你钱的时候没扣成功。这钱银行给的补助吗?嘿嘿,那银行肯定不开心。
所以了我们只能在这种操作中使用事务,来保证执行的成功与失败,失败了要进行回滚,保证扣钱的操作也不执行。
事务的ACID
事务具有四个特性,这四个特性简称为ACID
原子性Atomicity:同一组操作,要么做,要么不做,一组中的一两个执行成功不代表成功,所有成功才可以。这就是原子性,做或者不做(失败进行回滚)。 一致性Consistency:数据的一致性,就像上面的举例说的,你扣钱了,对方没加钱,那肯定不行。 隔离性Isolation:多个数据库操作同一条数据时,不能互相影响。不能你这边变动,那边数据空间就变换了。 持续性Durability: 事务结果提交后,变动就是永久性的,接下来的操作或者系统故障不能让这个记录丢失。
今天主要说的事务就是隔离。看看事务是怎么保证数据之间的隔离
事务的隔离级别
不同的事务隔离级别对应的不同的数据执行效率,隔离的越严格,那么执行的效率就越低下,下面的四个隔离级别是原来越严格。
读未提交(read uncommitted):指数据在事务执行时,还没有提交,其他事务就可以看到结果 读提交(read committed):指数据在其事务提交后,其他事务才能看到结果。「视图是在执行sql语句的时候进行创建,具体视图看下面的数据隔离是怎么实现的」 可重复读(repeatable read):一个事务在执行过程中,看到的结果与其启动的时候看到的内容是一致的。启动的时候会创建一个视图快照,该事务状态下,会看的一致是这个视图快照内容,其他事务变更是看不到的。「注意是读取的过程,如果是更新,那么会采用当前读,就是其他事务的更新操作会拿到结果,用来保证数据一致性」 串行化(serializable):顾名思义,就是将多个事务进行串行化(读写过程中加锁,读写冲突,读读冲突),一个事务结束后,另外一个事务才能执行,带来的效果就是并行化不好,效率低下。
Mysql中默认的事务隔离级别是可重复读,使用下面这个命令进行查看当前的事务级别,
show variables like 'transaction_isolation';
# 下面的语句进行修改事务的级别。
SET session TRANSACTION ISOLATION LEVEL Serializable;(参数可以为:Read uncommitted,Read committed,Repeatable,Serializable)
事务的启动方式
在程序中,我们很多时候都是默认的自动提交,也就是一个sql操作就是一条事务,但有时候需要的是多个SQL进行组合,我们就要显式的开启事务。
显示开启的语句是用 begin或者 start transaction.同样在事务结束的时候使用commit进行提交,失败使用rollbakc进行回滚。
当然如果不想让SQL进行自动提交,我们就将自动提交进行关闭set autocommit=0
,这样事务就不会自动提交,需要我们手动的执行commit.
如何避免长事务
关闭自动提交事务后,就需要我们来自己提交事务,这时候每个语句执行都是这样的。
begin
sql语句
commit
如果我们在程序编写中,本来一个sql解决的操作,结果忘记进行事务的提交,到下下下一个SQL才进行commit,这样就会出现长事务。
而长事务往往会造成大量的堵塞与锁超时的现象,事务中如果有读写(读读不冲突,读写冲突,写写冲突)操作,那么会将数据进行锁住,其他事务也要进行等待。
所以在程序中,我们应该尽量避免使用大事务,同样也避免我们写程序的时候出现偶然的大事务(失误?)。
解决办法是我们将自动提交打开,当需要使用事务的时候才会「显示的开启事务」。
程序中出现大量的事务等待怎么办
在MySQL中想定位一个长事务问题还是很方便的。
首先我们先找到正在执行的长事务是什么。
select t.*,to_seconds(now())-to_seconds(t.trx_started) idle_time from INFORMATION_SCHEMA.INNODB_TRX t \G
该语句会展示出事务的执行的开始时间,我们可以很简单的算出,当前事务执行了多久,其中上面的idle_time就是执行的事务时间
假设我们现在设定的超过30s执行的事务都是长事务,可以使用下面语句进行过滤30s以上的长事务。
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>30
通过information_schema.innodb_trx 我们就能定位到长事务。
从而决定长事务是否要被杀死,还是继续等待。如果出现死锁的情况,处理方式也是类似,查找到死锁的语句,然后进行杀死某个语句的执行(「有点暴力」)。
总结
本次只是了解了下事务的隔离级别以及启动方式与怎么定位长事务,关于数据隔离的实现放在了下一篇文章中。尽情期待。
往期文章一览