1.1 在研究索引管理之前,我们当然也先提出几个问题
-
MyISAM 与 InnoDB 关于锁方面的区别是什么?
-
数据库的四大特性?
-
事务隔离级别以及各级别下的并发访问问题?
-
InnoDB可重复读隔离级别下如何避免幻读?
-
RC、RR级别下的InnoDB的非阻塞度如何实现?
2.1 MyISAM 与 InnoDB 关于锁方面的区别是什么?
-
MyISAM 默认用的是表级锁,不支持行级锁
-
InnoDB 默认用的是行级锁,也支持表级锁
锁级别:
read锁 又可叫共享锁
write锁 又可叫排它锁
IS 共享读锁 IX 排他写锁
共享锁和排斥锁的兼容性
- | X | S |
---|---|---|
X | 冲 突 | 冲 突 |
S | 冲 突 | 兼 容 |
2.2 适合的场景
2.2.1 MyISAM 适合的场景
-
频繁执行全部 count 语句
-
对数据进行增删改的频率不高,查询非常频繁
-
没有事务
2.2.2 InnoDB 适合的场景
-
数据增删改查都相当频繁
-
可靠性要求比较高,要求支持事务
2.3 数据库锁的分类
-
按锁的粒度划分,可分为表级锁、行级锁、页级锁
-
按锁的级别划分,可分为共享锁、排它锁
-
按加锁的方式划分,可分为自动锁、显示锁
-
按操作划分,可分为 DML 锁、DDL 锁
-
按使用方式划分,可分为 乐观锁、悲观锁
ACID
-
原子性(Atomic)
-
一致性(Consistency)
-
隔离性(Isolation)
-
持久性(Durability)
3.1 事务隔离级别以及各级别下的并发访问问题
3.1.1 事务并发访问引起的问题以及如何避免
-
更新丢失—— mysql 所有事务隔离级别在数据库层面上均可避免
-
脏读—— READ-COMMITTED 事务隔离级别以上可避免
-
不可重复读—— REPEATABLE-READ 事务隔离级别以上可避免
-
幻读—— SERIALIZABLE 事务隔离级别可避免
事务隔离级别 | 更新丢失 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
未提交读 | 避免 | 发生 | 发生 | 发生 |
已提交读 | 避免 | 避免 | 发生 | 发生 |
可重复读 | 避免 | 避免 | 避免 | 发生 |
串行化 | 避免 | 避免 | 避免 | 避免 |
注:
- oracle 默认隔离级别:read-commited
- mysql 默认隔离级别:repeatable-read
4.1 InnoDB 可重复读 隔离级别下如何避免幻读
-
表象:快照度(非阻塞读)—— 为 MVCC
-
内在:next-key 锁(行锁+gap锁)
4.2 当前读和快照读
-
当前读:select...lock in share mode , select...for update
-
当前读:update , delete , insert
-
快照读:不加锁的非阻塞读,select
- 注
- RC 级别下
- 快照读 和 当前读 读到的数据版本是一样的 (新版本)
- RR 级别下
- 快照读
- 有可能读到的是历史版本
- 也有可能读到最新版本(事务首次调用快照读的地方很关键。创建快照的时机,决定读取数据的版本)
- 开启 事务1后,先不执行 select
- 等 事务2 提交后;再 select 获取的就是最新数据
- 还是有可能具备不让我们看到别的事务新增的**行的能力
- 当前读 读到的是最新版本
- 快照读
- RC 级别下
4.3 RR、RC 级别下的 InnoDB 的非阻塞读如何实现
-
每行数据行里的 DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID 字段
- DB_TRX_ID 最后一次对本行记录做 UPDATE 的 事务标识符
- DELETE 也是 UPDATE。有个 delete 隐藏列,会标识
- DB_ROLL_PTR 回滚指针
- 指写入回滚段(rollback segment) 的 undo 日志记录
- 如果一行记录被更新,则 undo log record 包含 重建该行记录,被更新记录之前内容所必需的信息
- DB_ROW_ID 行号
- 包含随着新行插入,单调递增的行ID (越新开启的事务ID,越大)
- 当有 Innodb 自动生成 聚集索引,聚集索引会包含 DB_ROW_ID;否则 DB_ROW_ID 不会出现在任何索引中
- 如果 Innodb 中的表,既没有 主键 也 唯一键,Innodb 会自动隐式创建一个 自增的 隐藏主键字段,既这里的 DB_ROW_ID
- DB_TRX_ID 最后一次对本行记录做 UPDATE 的 事务标识符
-
undo 日志
- 当对数据进行变更操作时,就会产生 undo log 记录
- undo 中存储的是老版数据
- 当一个旧的事务,需要读取数据时,为了能读取老版本数据,需要顺着 undo 链,找到满足其可见性的记录
- undo log 分为两种
- insert undo log
- 表示 insert 新记录产生的 undo log
- 只在事务回滚时需要,并且在事务提交后,就可以立即丢弃
- update undo log
- 事务在 delete 和 update 时产生的 undo log
- 此类 undo log,不仅在事务回滚时需要,快照读也需要,所以不能随便删除
- 只有当数据库所使用的快照中,不涉及该日志记录,对应的回滚日志,才会被 purge 线程删除
- insert undo log
事务对行记录的更新过程
-
被事务 a 修改,将 Field2 由 12-> 32
- 首先,用排它锁,锁定该行
- 随后,把该行修改前的值 copy 一份到 undo log 中
- 之后,修改当前行的值(Field2),填写事务id(DB_TRX_ID),使用回滚指针(DB_ROLL_PTR)指向 undo log 中被修改前的行
- 在这之后,假设数据库还有别的事务,再用 快照读 来读取该日志记录
- 那么对应的 undo log 还没有被清除,此时某个事务又对同一行数据做了修改
- 那么和前面一样,又多了一行 undo log,它们通过 DB_ROLL_PTR 给连接起来
-
read view
- 主要用来做可见性判断
- 即当一个事务去做 快照读 select 的时候,会针对 查询的数据,创建出一个 read view
- read view 来决定当前的事务能看到的是哪个版本的数据
- 有可能是当前最新版本的数据
- 也有可能只允许看 undo log 里,某个版本的数据
- read view 遵循一个可见性算法
- 主要是将要修改的数据的 DB_TRX_ID 取出来,与系统其他活跃事务ID(DB_TRX_ID) 做对比
- 如果 大于或等于 这些 DB_TRX_ID, 就通过 DB_ROLL_PTR 取出 undo log
- 上一层的 DB_TRX_ID,直到小于这些活跃事务ID(DB_TRX_ID)为止,这样就保证了,事务所获取的数据版本,是当前可见的最稳定的版本
- 主要用来做可见性判断
MySQL部分源码
涉及主要变量
Class ReadView:
...
private:
/** the read should not see any transaction with trx id >= this value.
In other words, this is the "high water mark". */
trx_id_t m_low_limit_id // 活动事务的最大id
/** The read should see all trx ids which are strictly smaller (<) than this value.
In other words, this is the "low water mark". */
trx_id_t m_up_limit_id // 活动事务的最小id
- 主要通过
m_low_limit_id
和m_up_limit_id
与 当前事务的 DB_TRX_ID 做对比,决定是否回溯到 undo log,去取出适应该版本的数据版本。
注:每当 start 一个 Transaction 时,事务ID 会递增。即:越新开启的事务ID,越大
-
正因为生成的时机不同,造成了 RC、RR 两种隔离级的不同可见性
- 在 repeatable read 级别下
- 在 start transaction 后的第一条快照读,会创建一个 快照,即 read view
- 将当前系统中活跃的其他事务,记录起来
- 此后在调用快照读时,还是使用 同一个 read view
- 在 repeatable committed 级别下
- 事务每条 select 语句,即每次调用快照读,都会创建一个新的快照
- 这就是为什么 RC 级别下,能用快照读 看到别的事务,已提交的对表记录的增、删、改
- 在 repeatable read 级别下
-
而在 RR 下,如果首次使用快照读,是在别的事务的数据进行增删改并提交之前的,此后即便别的事务对数据做了增删改并提交,还是读不到数据变更的原因,对于 RR 来说,首次 事务 Select 的时机相当重要
-
正因为上面三个因素,使得 innodb 在 RR 或者 RC 级别下,支持非阻塞读。而读取事务时的非阻塞就是所谓的 MVCC
-
而 innodb 的非阻塞机制,实现了仿照版的 MVCC(多版本并发控制) 读不加锁,读写无冲突;在读多写少的
-
思考
- OLTP 应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能,为什么 innodb 仅实现了 伪 MVCC 机制?
- 因为 innodb,并没有实现核心的多版本共存,undo log 的内容只是串行化的结果
- 记录了多个多个事务的过程,不属于多版本共存
- OLTP 应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能,为什么 innodb 仅实现了 伪 MVCC 机制?
5.1 next-key 锁(行锁+gap锁)
-
行锁
- 对单个行的锁
-
Gap 锁(Gap Locks)
- 间隙锁,索引树中插入新纪录的空隙,但不包括记录本身
- 主要避免同一事务的两次当前读,出现幻读的情况
- RR 和 串行化 默认支持 gap锁
-
以下对 RR 下,gap锁 出现的场景
5.2 对主键索引或者唯一索引是否会用 Gap 锁
- 如果 where 条件全部命中,则不会使用 Gap 锁,只会加 记录锁(Record Locks)
- 如何 where 条件部分命中或者全不命中,则会加 Gap锁
**锁了 (6,11],间隙锁范围还和主键的字母序大小有关 锁了 'c',6,插入 'bb',6 成功;而 'dd', 6 失败 **
-
purge 线程