MySql - Must Know

141 阅读6分钟

Page

InnoDB minimum store unit is page, and every page size is 16KB

Buffer Pool:

InnoDB会在启动时申请一块连续的内存(用户空间), 用于做CPU和磁盘的缓冲:

image.png

可能会用到mmap, 这样少一次内核空间与用户空间之间的拷贝

Index

索引使用B+ 树, 每个节点约有1200个叉, 树高为4时(算上根)就是17亿条数据

B+树只有叶子节点保存数据

B+树比哈希表的优点

  1. 范围查询快
  2. 支持最左原则

索引分为

  1. 主键索引(聚簇索引)
  2. 非主键索引(非聚簇索引)

日志

binlog

binlog主要用于 主从复制 和 数据回放

Mysql的server层日志, 只有(DDL DML)命令才会新增binlog, 且只有事务提交时才会将全部命令写到binlog

binlog has three format:(nomarly use MIXED)

  1. STATEMENT: store original sql order, 问题是SQL中的不确定函数(比如time())回放时会有问题, 优点是空间占用小
  2. ROW: store changed sql data, 问题是占用的空间大, 优点是回放精准
  3. MIXED: 不确定函数用ROW(like DATE()), 其他用STATEMENT

About Binlog Format : www.cnblogs.com/xinysu/p/66…

redolog

用于崩溃后的恢复和IO性能优化

因为一个事物可能修改到多个buffer pool page, 每个page又会随机读写不同磁盘, 如果直接写磁盘会有严重的IO瓶颈, 为了提高性能, 事务会顺序写redolog并更新内存, 然后会在操作系统空闲时将命令写到磁盘中以提高性能

redolog的数据储存格式是“对一个页做了什么修改”(物理日志)

redolog是Innodb的引擎层日志, the structure of redolog is a circle(four 1GB file):

image.png

2PC:

UPDATE A SET C=C+1 WHERE ID=2

Mysql would add begin commit as default

image.png

Use words to describe 2PC:

  1. server层的执行器调innodb的接口读到ID=2这一行record(innodb会先看buffer pool再读磁盘)
  2. 执行器在内存把这行record加1, 然后调innodb的写接口, innodb会将新的record更新到buffer pool, 然后写入redolog并写入磁盘, 此时redolog文件里的这个事务标记为prepare状态
  3. 执行器在收到innodb写接口返回后将命令写入binlog并写入磁盘
  4. 执行器执行commit命令, 调用innodb接口, innodbredolog文件写入commited状态

这样crash safe的处理就是:

  1. 如果崩溃时没有写入binlog, 恢复时redolog里的日志处于prepare状态则直接回滚
  2. 如果崩溃时写入binlog, 但redolog还没有commit, 则恢复时重新提交commit转为commited状态 (因为写入binlog说明很可能已经主从复制用到了)

undolog

上面提到crash safe时可能会回滚事务, 回滚便依赖于undolog

实际上, undo log提供了两个作用: 事务回滚和MVCC; undo log is a logic log which stores sql order:

image.png

undolog内容会写到redolog

ACID

REF: https://byte_dance.fei_shu.cn/wiki/wikcnq0RKomhKAVQGb4ZOXPTZtf

Isolation Level

isolation has four level:

  1. read uncommited: transaction A can read transaction B's uncommited record.
  2. read commited: transaction A can only read transaction B's commited record.
  3. repeated read: InnoDB默认隔离级别, 一个事务中多次读同一个SQL结果是一样的
  4. seriablized: 纯串行执行

What's phantom read?

幻读是事务A第一次读没有行C, 第二次就读到行C, 这是因为其他事物可能插入行C

Consistency

一致性是指事务执行前后数据库完整性约束没有被破坏, AID保证了C

image.png

dirty read & phantom read都能破坏Consistency

MVCC & Lock

Lock

从粒度上分:

  1. 表锁: Generally lock in server level
  2. 行锁: lock in innodb level

从兼容性来分:

  1. 共享锁: select xxx from xxx lock in share mode
  2. 排他锁: select xxx from xxx for update

并发读和写

当前读和快照读

In innodb only read commited and repeatable read has two read modes:

当前读指的就是使用锁来读:

  1. select xxx from xxx lock in share mode
  2. select xxx from xxx for update

快照读指不使用上面两种方式来读的都是快照度, 因为本质是读不同的副本, 都不是同一份资源所以不用加锁

In innodb the read uncommited level 用的就是不加锁的当前读

In innodb the serialization level 用的是table lock

写包括insert update delete 这些操作都是加X锁的

MVCC

多版本并发控制

上文的快照读innodb中指的就是MVCC(多版本并发控制), 一种无锁的并发读实现方案, 同理, MVCC只存在于read commited & repeatable read

对于reapeatable read来说, MVCC还能阻止phantom read, 因为只能读到小于等于当前事务ID的record(当前事务开启后, 再新插入的行的事物ID肯定大于当前ID

高可用

MySQL的高可用性解决方案目前大致分为6种,按照高可用的级别(99.9999%为最高级)排序依次为,主从复制、具有自动故障转移功能的主从复制、利用共享存储、OS或虚拟化软件实现主备架构、MySQL Group Replication 群组复制,以及 MySQL NDB Cluster

image.png

主从复制

Master-Slave Replication, ensure 99% availability

image.png

As traditional master-slave replication, master doesn't care about whether slave received binlog. So there would be a case that master has commited the transaction and hasn't send binlog to the slave, so this binlog would be lost.

To solve this problem and improve availability, we can use semi-syncronous replication

半同步复制指的是指不仅master要运行事务, 还要确保至少有一个slave也已经收到了这个事务的binlog

异地多活

参考这篇文章 kaito-kidd.com/2021/10/15/…

Abase异地多活的实现 https://tech.byte_dance.net/articles/7096127791057338404?from=lark_all_search

分库分表

分库

分库解决的是并发量过大问题, 数据库同时链接的句柄太多了

分表

分表解决的是数据量过大问题, 存储和IO遇到了瓶颈, 也就是单次查询性能不够了

横向分表:

技术难点是如何实现全局ID:

  1. UUID
  2. 设定自增步长
  3. ID分区, 比如0-1000, 1001-2000...

纵向分表:

讲不同的列分到不同的表

缓存一致性

看这篇文章: kaito-kidd.com/2021/09/08/…

先更新数据库, 再删除缓存

为了解决第二步失败(这里指删除缓存失败), 需要订阅binlog数据流(binlog发到mq里), 在另一个系统里来做删redis缓存的操作

延迟双删: 解决的问题是: 主库更新了A=新, 然后删除了缓存A, 但从库还没同步导致A=旧, 此时有读请求, 读完A=旧, 并更新到了缓存使A=旧, 然后主库同步才更新到从库, 使从库A=新, 这就导致了从库的A和缓存的A数据不一致

解决的方法是: 「延迟双删」:除了binlog删除缓存之外, 再起一个延时消息, 过一段时间后再删一次缓存

REF

  1. mp.weixin.qq.com/s/rcPkrutiL…