MySQL 事务隔离
先思考几个问题。

数据库的隔离级别都有什么? 数据库的MVVC视图是怎么实现的? 数据库的隔离级别是为了解决什么问题的?

数据库的事务

数据库的事务简单来说就是用来保证数据的正确性,它只有两个操作:事务要么成功,要么失败并进行回滚。
为什么这么做呢?这是因为一般进行事务操作,都会进行一组操作。比如常见的金融转账。
在这个转账事务里面包含2个操作:

  • 扣自己银行账户的钱
  • 给对应的账户添加收到的钱。

现在思考下,如果没有添加事务,那么会出现什么样的情况呢?

  1. 如果先扣钱成功,执行给别人加钱失败。而钱已经扣了,对方没收到钱,怎么办?
  2. 如果先给对方加钱,而扣钱的时候没扣成功。这钱银行给的补助吗?,那银行肯定不开心。所以了只能在这种操作中使用事务,来保证执行的成功与失败,失败了要进行回滚,保证扣钱的操作也不执行。

    事务的ACID

    事务具有四个特性,这四个特性简称为ACID
  • 原子性Atomicity:同一组操作,要么做,要么不做,一组中的一两个执行成功不代表成功,所有成功才可以。这就是原子性,做或者不做(失败进行回滚)。
  • 一致性Consistency:数据的一致性,就像上面的举例说的,扣钱了,对方没加钱,那肯定不行。
  • 隔离性Isolation:多个数据库操作同一条数据时,不能互相影响。不能这边变动,那边数据空间就变换了。
  • 持续性Durability: 事务结果提交后,变动就是永久性的,接下来的操作或者系统故障不能让这个记录丢失。

事务是怎么保证数据之间的隔离

事务的隔离级别

不同的事务隔离级别对应的不同的数据执行效率,隔离的越严格,那么执行的效率就越低下,下面的四个隔离级别是原来越严格。

  • 读未提交(read uncommitted):指数据在事务执行时,还没有提交,其他事务就可以看到结果
  • 读提交(read committed):指数据在其事务提交后,其他事务才能看到结果。「视图是在执行sql语句的时候进行创建,具体视图看下面的数据隔离是怎么实现的」
  • 可重复读(repeatable read):一个事务在执行过程中,看到的结果与其启动的时候看到的内容是一致的。启动的时候会创建一个视图快照,该事务状态下,会看的一致是这个视图快照内容,其他事务变更是看不到的。「注意是读取的过程,如果是更新,那么会采用当前读,就是其他事务的更新操作会拿到结果,用来保证数据一致性」
  • 串行化(serializable):顾名思义,就是将多个事务进行串行化(读写过程中加锁,读写冲突,读读冲突),一个事务结束后,另外一个事务才能执行,带来的效果就是并行化不好,效率低下。

Mysql中默认的事务隔离级别是可重复读,使用下面这个命令进行查看当前的事务级别,

  1. show variables like 'transaction_isolation';
  2. # 下面的语句进行修改事务的级别。
  3. SET session TRANSACTION ISOLATION LEVEL Serializable;
  4. (参数可以为:Read uncommittedRead committedRepeatableSerializable

image.png

事务的启动方式

在程序中,很多时候都是默认的自动提交,也就是一个sql操作就是一条事务,但有时候需要的是多个SQL进行组合,就要显式的开启事务。
显示开启的语句是用 begin或者 start transaction.同样在事务结束的时候使用commit进行提交,失败使用rollbakc进行回滚。
当然如果不想让SQL进行自动提交,将自动提交进行关闭 set autocommit=0 ,这样事务就不会自动提交,需要手动的执行commit.

如何避免长事务

关闭自动提交事务后,就需要来自己提交事务,这时候每个语句执行都是这样的。

  1. begin
  2. sql语句
  3. commit

如果在程序编写中,本来一个sql解决的操作,结果忘记进行事务的提交,到下下一个SQL才进行commit,这样就会出现长事务。
而长事务往往会造成大量的堵塞与锁超时的现象,事务中如果有读写(读读不冲突,读写冲突,写写冲突)操作,那么会将数据进行锁住,其他事务也要进行等待。
所以在程序中,应该尽量避免使用大事务,同样也避免写程序的时候出现偶然的大事务。
解决办法是将自动提交打开,当需要使用事务的时候才会「显示的开启事务」。

程序中出现大量的事务等待怎么办

在MySQL中想定位一个长事务问题还是很方便的。
首先先找到正在执行的长事务是什么。

  1. select t.*,to_seconds(now())-to_seconds(t.trx_started) idle_time from INFORMATION_SCHEMA.INNODB_TRX t \G

该语句会展示出事务的执行的开始时间,可以很简单的算出,当前事务执行了多久,其中上面的idle_time就是执行的事务时间
假设现在设定的超过30s执行的事务都是长事务,可以使用下面语句进行过滤30s以上的长事务。

  1. select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>30

通过information_schema.innodb_trx 就能定位到长事务。
从而决定长事务是否要被杀死,还是继续等待。如果出现死锁的情况,处理方式也是类似,查找到死锁的语句,然后进行杀死某个语句的执行(「有点暴力」)。