Mysql并发

Mysql并发

在事务并发的情形下会出现哪些?

  1. 丢失更新:更新操作并不是原子操作,在并发可能出现同时更新同一行,这时其中一个更新操作就会丢失。
  2. 脏读:其它事务修改了数据并回滚,导致读到错误数据。
  3. 不可重复读:其它事务修改了数据,导致前后读不一致。
  4. 幻读:不可重复读的特殊情况,其它事务插入或删除数据,导致前后读不一致。

如何解决这些问题?

使用锁来处理并发问题,不同的加锁机制可以解决不同的问题。

如何加锁(加锁时机,释放时机,读写锁)?

根据加锁的机制可以归为三层封锁协议:

  1. 第一级封锁协议:修改数据前加写锁,持续到事务结束。防止了丢失更新。
  2. 第二级封锁协议:修改数据前加写锁,持续到事务结束。读取数据前加读锁,读取数据后释放。防止了脏读。
  3. 第三级封锁协议:修改数据前加写锁,持续到事务结束。读取数据前加读锁,持续到事务结束。防止了不可重复读。

如何处理死锁的问题?

三层封锁都会出现死锁问题
处理死锁的方案跟多,如按顺序加锁和释放,有需要再深究

封锁协议和Mysql隔离级别的关系?

大致对应关系:

  • 第一级封锁协议对应RU(READ UNCOMMITTED)
  • 第二级封锁协议对应RC(READ COMMITTED)
  • 第三级封锁协议对应PR(REPEATABLE READ)

第二级封锁协议和Mysql RC隔离级别的区别?

第二级封锁协议中,获取读锁必须等待写锁释放,这会带来一定的性能阻塞。
Mysql使用MVCC(多版本并发控制)实现第二级封锁协议,可以做到不加锁读非阻塞读取数据

MVCC实现简单原理?

InnoDB为每个记录维护三个隐藏字段:

  • ID:唯一编号
  • DB_TRX_ID:当前处理事务ID
  • DB_ROLL_PTR:回滚指针,指向历史记录中的ID

InnoDB同时会维护两个隐藏表:

  • redo_log:记录历史操作
  • undo_log:记录历史数据

MVCC

DML对应的操作结果:

  • INSERT:新插入一行,保存当前事务编号作为行版本号
  • DELETE:加入undo_log中,并保存当前事务编号作为行版本号
  • UPDATE:INSERT后DELETE,并将回滚指针指向历史数据

SELECT比较特殊,需要根据read view进行判断
read view是InnoDB维护的一个当前系统中的活跃事务列表,当前事务与正在内存中commit的事务不在活跃事务列表中
SELECT的判断如下:
某行的事务id为trx_id,read view中最早的事务id为trx_id_min, 最迟的事务id为trx_id_max

  • trx_id< trx_id_min,表明该行记录所在的事务已经在本次新事务创建之前就提交了,可见
  • trx_id>trx_id_max,表明该行记录所在的事务在本次新事务创建之后才开启,不可见,之后索引DB_ROLL_PTR
  • trx_id_min <= trx_id <= trx_id_max,若read view包含trx_id,不可见,否则可见,之后索引DB_ROLL_PTR

MVCC SELECT的特殊性:

MVCC中的SELECT只能读到当前事务可见的版本数据,所以是一种快照读,但是在UPDAET,DELETE,INSERT以及LOCK IN SHARE MODE 和 FOR UPDATE中,对数据存在性的判断读都是当前读,而且,快照读和当前读并不能解决幻读的问题

MVCC对写是加锁的

MVCC在PR中和在RC中的区别?

RC解决脏读,PR解决不可重复读,但是MVCC正好是在这两种级别下都工作的,关键在于生成read view的机制不同:

  • 在RC中,read view在每个操作前都会重新生成
  • 在PR中,read view在事务开始时生成,并持续到事务结束

PR隔离防止幻读了吗?

第三级封锁协议不能解决幻读,快照读和当前读也不能解决幻读的问题,但是奇怪的是Mysql的PR级别确实是防止幻读了,到底是怎么回事呢?

原因是,Mysql会给范围读加间隙锁,使得期间无法删除或者插入数据,由此解决了幻读,但是我们不才说了SELECT是不加锁的么?

这是因为,除了SELECT外,还有两个特殊的读LOCK IN SHARE MODE 和 FOR UPDATE,他们是以加锁的实现的

LOCK IN SHARE MODE和FOR UPDATE的区别?

LOCK IN SHARE MODE加读锁
FOR UPDATE加写锁