InnoDB|带你认识什么是MySQL的内存架构和索引说明( 六 )


InnoDB中的死锁因为不同的事务持有对方需要的锁 , 导致这些事务不能执行下去 。 由于每个事务都在等待资源变成可用 , 都不会释放它持有的锁 。
可能出现的场景:多个事务以不同的顺序锁定多个表中的行(通过类似这样的语句:UPDATE或SELECT … FOR UPDATE) , 锁定范围索引记录和间隙 , 每个事务因为时间的原因只获取一部分锁 。
为了减少死锁的可能 ,

  • 使用事务而不是LOCK TABLES语句;
  • 保持insert或update的事务足够小 , 让他们不会长时间打开;
  • 当不同的事务更新多个表或者大范围的行 , 在每个事务里使用相同顺序的操作(例如SELECT … FOR UPDATE);
  • 在SELECT … FOR UPDATE和UPDATE … WHERE 语句中使用到的列上创建好索引
  • 如果使用锁定读(SELECT … FOR UPDATE或者SELECT … FOR SHARE) , 尝试使用例如READ COMMITTED这样的低隔离级别
  • 添加选择度高的索引到表中 , 这样查询只需要扫描更少的索引记录 , 相应的也就设置更少的锁 。 使用EXPLAIN SELECT来查看
死锁的可能不会被隔离级别影响 , 因为隔离级别只改变了读操作的行为 , 而死锁是因为写操作 。
InnoDB多版本控制 MVCCInnoDB是一个多版本的存储引擎 。 它持有被更改过的行的旧版本信息 , 以支持例如并发和回滚这样的事务特性 。 这个信息存储在undo表空间里面一个叫rollback segment的数据结构中 。 InnoDB使用回滚段里的信息来执行事务回滚里面的undo操作 。 它还使用这信息来构建更早版本的行用于实现一致性读 。
InnoDB内部在数据库里存储的每个行里添加了三个字段:
  • 6-byte DB_TRX_ID , 表示最近一个插入或更新行的事务的事务标识符 。 删除在内部也是被视为更新 , 行里面一个特定的bit会设置以标识它为已删除
  • 7-byte DB_ROLL_PTR , 滚动指针 。 它指向回滚段里的一条undo日志记录 。 如果行被更新了 , 这条undo日志记录会包含更新前重建行所需要的必要信息
  • 6-byte DB_ROW_ID , 包含了随着新行插入而单调递增的行ID 。 如果InnoDB自动生成了一个聚簇索引 , 索引会包含行ID的值 。 否则DB_ROW_ID列不会出现在任何索引里
回滚段里的Undo日志被分成了插入和更新的undo日志 。 插入的undo日志只在事务回滚的时候需要 , 可以在事务提交的时候立即丢弃掉 。 更新的undo日志也用于一致性读 , 但只有在不存在InnoDB已为其分配快照的事务时才能丢弃 。 在一致性读中 , 快照需要更新的undo日志中的信息用于构建数据库行的早期版本 。
建议定期提交事务 , 包括仅发出一致性读的事务 。 否则InnoDB不能从更新的undo日志里丢弃数据 , 这样回滚段可能会增长得太大 , 充满它驻留的整个undo表空间 。
回滚段里的undo日志记录的物理大小通常比相应的插入或更新行更小 。 可以使用这个信息来记录回滚段需要的空间 。
在InnoDB多版本控制方案中 , 当使用SQL语句删除的时候 , 该行并不会立即从数据库中物理删除 。 InnoDB仅在丢弃为删除而写入的更新undo日志的时候 , 才会物理删除相应的行和索引记录 。
多版本和二级索引InnoDB 多版本并发控制(MVCC)对待二级索引不同于聚簇索引 。 聚簇索引中的记录会就地更新 , 其隐藏的系统列指向undo日志项 , 从中可以重构早期版本的记录 。 二级索引不包含隐藏的系统列 , 也不进行就地更新 。
更新二级索引列时 , 旧的二级索引被删除标记 , 新记录被插入 , 删除标记的记录最终被清除 。 当二级索引记录被删除标记 , 或者二级索引页被更新的一个事务更新时 , InnoDB会在聚簇索引中查找数据库记录 。 在聚簇索引中 , 检查记录的DB_TRX_ID , 如果在读取事务启动后修改了记录 , 则从undo日志里检索出记录的正确版本 。