1-Intro
2-InnoDB mvcc
MVCC 是 InnoDB 的特性. Mysql 是面向 Row 的设计, 有三个隐藏字段:
• DB_TRX_ID:这个字段保存了最后修改该行的事务ID。在处理读请求时,如果发现某行的DB_TRX_ID值大于当前事务的ID,那么表示该行在当前事务开始后被修改过,那么InnoDB就需要通过DB_ROLL_PTR找到对应的undo日志,获取该行在当前事务开始时的版本。
• DB_ROLL_PTR:这个字段是一个回滚指针(roll pointer),它指向一个undo日志记录。如果发现行记录在当前事务开始后被修改过,就需要通过这个回滚指针找到记录修改前的版本。这个字段构成了一个链表结构,可以一直回溯到更早的版本。
• DB_ROW_ID:该字段保存了一行记录的ID,在InnoDB为表自动创建的聚簇索引(也就是主键索引)中使用。这个字段不涉及到MVCC相关的操作
个人理解-如何实现 RepeatAble Read: Mysql 有一个递增的 事务 ID, 每次开始事务使用一个新的 TX_ID.
-
在查询到一行数据的时候, 会选择一个一个版本, 这个版本的
DB_TRX_ID必须不超过当前的TX_ID, 证明不是事务之后的修改. -
DB_ROLL_PTR指向UNDO_LOG中的数据,代表修改之前,通过这个PREV指针组成了一个链表结构, 可以一直往前寻找之前的版本
因此:
- 当前事务中的修改,造成的
DB_TRX_ID是可以被看到的. - 之前的事务中的修改是看到的
- 之后的事务中的修改是看不到的
3-InnoDB 索引
B+ 树索引
聚簇索引
**
- 本质就是 叶子节点同时保留了数据,不需要回表, 如上图, 因此 Netflix Dblog - CDC 中使用
MYSQL的聚簇索引去做全量DUMP也是比较高效的; - 聚簇索引 不是物理连续的, 是逻辑连续的. 这里有2点:
Page用双向链表的形式组织的, 每个Page中的记录不同的ROW也是通过双向链表连接的 ;
关于字段的区分度
mysql> show index from message_message ;
+-----------------+------------+---------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------------+------------+---------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| message_message | 0 | PRIMARY | 1 | id | A | 你猜猜 | NULL | NULL | | BTREE | | |
| message_message | 1 | idx_photo_id | 1 | photo_id | A | 你猜猜 | NULL | NULL | YES | BTREE
- 使用
show_index可以看到索引某个字段的区分度. 上面的你猜猜是编的 cardinality的统计算法很有意思:- 更新触发有2个条件:
- 表中
1/16的数据发生了修改 stat_modified_counter > 2 000 000 000, 这个计数器代表变化的次数
- 表中
- 随机抽取 8个叶子节点 抽样反推, 所以是一个 预估值
- 更新触发有2个条件:
联合索引
- 最左前缀: 个人理解,所有走 字段 拼接算法来做 联合索引的 都有这个问题.
下面是 个人理解:
- 最大的好处, 是 多个字段都在索引中, 能直接在索引中做
filter,sort,range这样的工作, 受到 最左前缀的约束 ; - 其实 联合索引肯定性能不如单个索引的,因为
key_len更大,意味着在 叶子节点不能存储更多的内容, 也是不一定要删除那个单个索引,如果查询的需求特别猛的话 ;
覆盖索引: covering index
- 这个是指
secondary index的叶子上存储了 主键,所以如果 返回的字段都在索引中,可以减少一次 回表 - 对
count(*)的帮助, 优化器会认为 其实secondary index中也有主键信息,而secondary index可能远远比 聚集索引要小, 所以走secondary index做统计会减少IO操作, 这也是 推荐COUNT(**)而不是COUNT(id)的一个可能原因, 能更有效的利用索引, 当然要注意COUNT(COLUMN)需要做一个NOT NULL的工作,所以建议字段能NOT NULL还是NOT NULL;
INDEX HINT
有2种情况可以考虑使用 INDEX HINT.
- 对某条
SQL语句Mysql选择了错误的索引, 新版本的.Mysql非常的少见, 优化器还是很屌的, 会基于统计信息来优化 ; - 对某条
SQL语句,可能的索引太多了,优化器选择的开销可能超过了本身 ;
注意 USE INDEX 是建议, FORCE INDEX 才是强制, 前者只是建议,不一定生效的.
MRR :
Multi-Range-Read.
- 用
explain的话说,有三种情况会用到,range,ref,eq_ref可能用到
mysql> SELECT @@optimizer_switch;- 可以看到
mrr的开启状态,一般都是开了的 ; mrr可以把 随机IO 转为顺序IO, 所以SSD上没有机械磁盘明显,但是还是更快 ;
举个例子, key_part_1 和 key_part_2 组成了联合索引
SELECT * FROM t WHERE key_part_1 >= 1000 AND key_part_1 < 2000 AND key_part_2 = 10000;从最左前缀的角度,上面仅仅能用到 key_part1 :
- 根据
key_part_1取出来[1000,2000)的数据, 然后再回表拿key_part_2filter
从 MRR 的角度的一个选择: 条件拆分为 in .
(1000, 10000)(1001, 10000)…
这个角度说明了 Mysql 的优化器是非常复杂的.
ICP: Index Condition Pushdown
- 谓词下推
还是上面的例子. 可以有 第三个谓词下推的选择. 上面是说根据 key_part_1 取出来 [1000,2000) 的数据, 然后再回表拿 key_part_2 filter.
其实索引中已经有了 key_part_2, 可以不用回表, 直接取出索引中的另外一部分数据来帮助 filter
个人理解,这其实是为了更 充分的利用联合索引,对最左前缀原则的补充.
InnoDb 的 hash 引擎: 注意不是自适应 Hash, 主要是引入一下 InnoDb 的 hash 算法
Hash冲突使用的也是链表法- 在
InnoDB Buffer Pool中有专门的区域来缓存. - 假设
innodb_buffer_pool=10M如果没有开启大页,就有640个16KB的页, 就有> 640 x 2的质数也就是1399个slot的Hash表.
这个 Hash 表代表了一个 Page, 用来加速页的定位. 每个 Page 在 Buffer Pool 都会有一个 Hash 来加速定位.
InnoDB 的 自适应 Hash 索引
- 使用上面的 hash 表算法实现
- 缓存的是
SELECT * FROM TABLE WHERE index_col = 'xxx'
是对 B+ 树索引的补充, 必须是 索引字段才能使用. 同样存储在 Buffer Pool 中. 缓存的是 索引列的值→ Page Id , 我们知道 Buffer Pool 是多实例的,每个实例都有自己的 自适应 Hash 内存, 减少并发冲突 ;
4-TokuDB 索引
特点是为了 写入的场景优化了 B+ 树的问题 引入了它的变种,叫做 Fractal Tree .
Message Buffering: 传入的写入请求先缓存起来,然后批处理写- 内置数据压缩
适合读少写多的场景
5. 锁
行级锁
InnoDB支持行级锁. 有2种:S: 允许当前读取X: 允许当前删除或者修改
上面简单的介绍了一下 MVCC 其实就是 事务的无锁读取.
READ_UNCOMMITED: 永远去读取最新的版本READ_COMMITED: 永远读取最新的版本,如果当前的行被锁定去读取最新的快照REPEATABLE_READ: 读取 ⇐ 当前的事务ID 的版本
一张图说明原理.

行锁有三种算法:
Record Lock: 单个记录上的锁Gap Lock: 间隙锁, 仅仅包含了范围Next-Key:Gap+Record,既包含记录 又包含了 范围
基于 Next-Key 锁解决了 幻读的问题.
SELECT * FROM t where a > 2 FOR UPDATE;- 会锁定记录和区间,这样 就会对这个 记录和区间加锁.