MySQL存储引擎

目录

MySQL存储引擎

一、MyISAM

1.1 了解 MyISAM

1.2 面试必考——MyISAM 和 InnoDB 对比

1.3 面试必考——MVCC是什么?

二、InnoDB

2.1 了解 InnoDB

2.2 InnoDB 体系架构

2.3 InnoDB 内存池

2.4 InnoDB 后台线程

2.5 InnoDB 核心特性

三、其他存储引擎


MySQL存储引擎

一、MyISAM

不支持事务,性能更好,数据占用空间小。锁级别为表级锁,并发性差。

1.1 了解 MyISAM

创建一个存储引擎为 MyISAM 的数据表

CREATE TABLE `isam_table` (
  `id` int(11) DEFAULT NULL,
  `create_time` VARCHAR(10) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

每张表对应会生成三个文件

-rw-rw---- 1 mysql mysql    493 Oct 16 02:39 isam_table.frm
-rw-rw---- 1 mysql mysql      0 Oct 16 02:39 isam_table.MYD
-rw-rw---- 1 mysql mysql   1024 Oct 16 02:39 isam_table.MYI

frm 文件:存储与表相关的元数据(meta)信息,包括表结构的定义信息等,每一张表都会有一个 frm 文件。

MYD 文件:MyISAM 存储引擎专用,存放 MyISAM 表的数据(data),每一张表都会有一个 .MYD 文件。

MYI 文件:MyISAM 存储引擎专用,存放 MyISAM 表的索引相关信息,每一张 MyISAM 表对应一个 .MYI 文件。

表修复

-- 检查表状态
check table `isam_table`;
-- 损坏表修复
repair table `isam_table`;

MySQL 还提供了一个工具

# 使用这个工具进行表修复必须停止 MySQL
myisamchk --help

表压缩

myisampack -b -f isam_table.MYI

压缩后的文件(.OLD 是压缩前的文件):

-rw-rw---- 1 mysql mysql    493 Oct 16 02:39 isam_table.frm
-rw-rw---- 1 mysql mysql     50 Oct 16 02:39 isam_table.MYD
-rw-rw---- 1 mysql mysql   1024 Oct 16 02:56 isam_table.MYI
-rw-rw---- 1 mysql mysql      0 Oct 16 02:39 isam_table.OLD

TIPS

压缩后的表是只读的

表大小限制

MySQL 5.0 之前,默认 4 G。可修改 MAX_RowsAVG_ROW_LENGTH 调整。

MySQL 5.0 时默认 256 TB。

适用场景

  • 非事务型应用

  • 只读类应用(可以压缩表)

  • 空间类应用(存储空间数据,又称几何数据,如 gps)

1.2 面试必考——MyISAM 和 InnoDB 对比

  • 事务和外键

    • InnoDB 支持事务,具有安全性和完整性,适合大量 insert、update 操作。

    • MyISAM 不支持事务,提供高速存储和检索,适合大量 select 操作。

  • 锁机制

    • InnoDB 支持行级锁,基于索引实现。

    • MyISAM 只支持表级锁。

  • 索引结构

    • InnoDB 适用聚集索引(聚簇索引),索引和记录在一起存储,既缓存索引,也缓存记录。

    • MyISAM 使用非聚集索引(非聚簇索引),索引和记录分开。

  • 并发处理能力

    • InnoDB 读写阻塞与隔离级别有关,可以采用多版本并发控制(MVCC)来支持高并发。

    • MyISAM 使用表锁,会导致写操作并发率低,读写阻塞。

  • 存储文件

    • InnoDB 对应两个文件: 表结构文件 .frm 、数据文件 .ibd;表最大为 64TB。

    • MyISAM 对应三个文件:表结构文件 .frm、表数据文件.MYD、索引文件.MYI;从 MySQL5.0 开始默认限制是 256TB。

存储引擎适用场景
InnoDB需要事务支持 并发要求高 写频繁 数据一致性要求高 硬件设备内存较大,可以利用 InnoDB 较好的缓存能力提高内存利用率,较少磁盘 IO
MyISAM不需要事务支持 低并发 读频繁(写操作少或只读) 数据一致性要求不高

1.3 面试必考——MVCC是什么?

MVCC(Multi-Version Concurrency Control)多版本并发控制,为了实现读不加锁而引入的机制。

只在 READ COMMIT 和 REPEATABLE READ 这两个事务隔离级别下生效。

因为 READ UNCOMMITTED 总是读取最新数据行,而 SERIALIZABLE 对所有操作都加锁(串行)。

MVCC的实现是通过 保存某个时间点的快照(Read View) 来实现的。也就是说:

  • 同一事务看到的数据都是一致的;

  • 不同事务开启时间不同,同一时刻看到的数据可能不一样;

TIPS

MVCC 没有规范,各个存储引擎和数据库的实现都是不一样的。

InnoDB 的 MVCC 是通过在每行记录后面保存一些额外信息:

作用
DATA_TRX_ID [6bit]最后一次更新这条记录的 transaction id,每处理一个事务,+1
DATA_ROLL_PTR [7bit]指向当前记录项的 rollback segmentundo log 记录,找之前版本的数据就是通过这个指针,更新失败时回滚用。
DB_ROW_ID [6bit]当由 InnoDB 自动产生聚集索引时,聚集索引包括这个 DB_ROW_ID 的值; 反之,聚集索引中不包括这个值,这个用于索引当中。
DELETE BIT标识该记录是否被删除,这里的不是真正的删除数据,真正意义的删除是在 commit 的时候。
  • SELECT

    • 只查找版本早于(包含)当前事务版本的数据行。

    • 只查找删除标识未定义的数据行。

  • INSERT

    • 为新插入的每一行保存当前系统版本号作为行版本号。

  • DELETE

    • 为删除的每一行保存当前系统版本号作为行删除标识。

  • UPDATE

    • 插入一行新纪录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。

如何理解 "保存当前系统版本号到原来的行作为行删除标识" ?

更新操作本质上也是新增一行,只是更新了旧的数据行的删除标识,同时更新了版本号,当删除标识不为空时版本号也称为:删除版本号 or 过期版本号。

二、InnoDB

2.1 了解 InnoDB

表空间

InnoDB 使用表空间存储数据。

SHOW VARIABLES LIKE 'innodb_file_per_table';
  • ON: 独立表空间 ,每张 InnoDB 表对应一个 tablename.ibd 文件。

  • OFF: 系统(共享)表空间 ,所有表共同使用一个(或多个,自行配置)ibdata 文件。

使用独立表空间的优点:

  • 可以通过 optimize table 命令收缩系统文件,大表或备份时节省空间。

  • 可以同时向多个文件刷新数据,对于频繁写的应用来说 IO 瓶颈高。

表转移

把原来存在于系统表空间的表转移到独立表空间,步骤如下:

  1. 使用 mysqldump导出所有数据库表数据;

  2. 停止 MySQL 服务,修改参数,并删除 Innodb 相关文件;

  3. 重启 MySQL 服务,重建 Innodb 系统表空间;

  4. 重新导入数据。

2.2 InnoDB 体系架构

  • 内存池

    • 维护多个内部数据结构。

    • 缓存磁盘上的数据,同时在对磁盘文件的数据修改之前在这里缓存。

    • 重做日志(redo log)缓冲。

    • ... ...

  • 后台线程

    • 负责刷新内存池中的数据,保证缓存是最新的。

    • 将已修改的数据文件刷新到磁盘文件。

    • 保证数据库发生异常时 InnoDB 能恢复到正常运行状态。

    • ... ...

2.3 InnoDB 内存池

2.3.1 认识缓冲池

InnoDB 是基于磁盘存储的,并将其中的记录按照 "页" 的方式进行管理。

由于 CPU 与磁盘之间速度的鸿沟,数据库使用缓冲池技术来提高数据库的整体性能。

 

  • 缓冲池大小

##################################
# 建议设置为总内存大小的 60%-80%
##################################
​
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
  • 从 InnoDB 1.0.x 开始,允许有多个缓冲池实例

    • 优点:减少数据库内部资源竞争,增加并发处理能力;

    • 每个 Page 根据哈希值平均分配到不同缓冲池实例。

mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';
+------------------------------+-----------+
| Variable_name                | Value     |
+------------------------------+-----------+
| innodb_buffer_pool_instances | 1         |
+------------------------------+-----------+
  • 查看缓冲池运行状态(show 命令)

mysql> SHOW ENGINE INNODB STATUS\G;
​
...
​
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 167772160
Dictionary memory allocated 1067424
Buffer pool size   8065
Free buffers       5625
Database pages     2440
Old database pages 920
Modified db pages  0
Percent of dirty pages(LRU & free pages): 0.000
Max dirty pages percent: 90.000
Pending reads 0
Pending writes: LRU 0, flush list 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 2309, created 131, written 0
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 2440, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
​
...
​
  • 查看缓冲池运行状态(information_schema.INNODB_BUFFER_POOL_STATS)

mysql> use information_schema;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
​
Database changed
MariaDB [information_schema]> SELECT POOL_ID, POOL_SIZE, FREE_BUFFERS, DATABASE_PAGES FROM INNODB_BUFFER_POOL_STATS\G;
*************************** 1. row ***************************
       POOL_ID: 0
     POOL_SIZE: 8065
  FREE_BUFFERS: 5625
DATABASE_PAGES: 2440
  • 查看缓冲池配置参数

# page 大小,默认为 16K
mysql> SHOW VARIABLES LIKE 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
​
# 缓冲池参数
mysql> SHOW VARIABLES LIKE '%innodb_buffer%';
+-------------------------------------+----------------+
| Variable_name                       | Value          |
+-------------------------------------+----------------+
| innodb_buffer_pool_chunk_size       | 134217728      |
| innodb_buffer_pool_dump_at_shutdown | ON             |
| innodb_buffer_pool_dump_now         | OFF            |
| innodb_buffer_pool_dump_pct         | 25             |
| innodb_buffer_pool_filename         | ib_buffer_pool |
| innodb_buffer_pool_instances        | 1              |
| innodb_buffer_pool_load_abort       | OFF            |
| innodb_buffer_pool_load_at_startup  | ON             |
| innodb_buffer_pool_load_now         | OFF            |
| innodb_buffer_pool_size             | 134217728      |
+-------------------------------------+----------------+

2.3.2 缓冲池管理

InnoDB 按照 Page(页)的方式管理数据,使用 LRU(Least Recently Used,最近最少使用)算法 进行 Page(页)的管理。

页的类型说明存在位置
free page空闲页,未使用Free 列表
clean page干净页,未修改过,与磁盘中的数据一致LRU 列表
dirty page脏页,修改过,与磁盘中的数据不一致LRU列表、Flush 列表

 

【普通 LRU 算法】

LRU 列表头部为使用最频繁的 Page,尾部为最少使用的 Page。当内存不足继续从磁盘读取 Page 时,释放尾部的 Page。

【存在的问题】

有一些操作需要访问表中的许多页,甚至全部页(如:索引 or 数据的扫描操作),这些页仅在这次查询操作中需要,并非热点数据;

如果直接把这些页放到 LRU 列表头部,那么就会有很多热点数据页被刷新出去,影响缓冲池效率,而且下次读取得重新访问磁盘。

【引入改良版 LRU 算法】

  • 使用 中点插入策略(midpoint insertion strategy),最新访问的页放入 LRU 列表的 midpoint 位置。

  • midpoint 可以通过参数 innodb_old_blocks_pct 设置,默认为 37,即 LRU 列表从尾部开始 37% 的位置(约 3/8)。

参数设置(非常重要) 

-- LRU 列表的 midpoint 位置(%),默认 37,热点数据越多,这个值就设置得越小
SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
SET GLOBAL innodb_old_blocks_pct=20;
​
-- 页(Page)读取到 midpoint 位置后,无法进入 LRU 列表的热端
-- 必须等待 1000 毫秒(默认值)后,下一次对页的访问才会使其移动到 LRU 列表的热端
SHOW VARIABLES LIKE 'innodb_old_blocks_time';

2.3.3 日志缓存

日志信息会先放到缓冲区,然后按照一定频率刷新到日志文件。日志包含:

  • InnoDB 引擎日志

  • 数据库操作时产生的 redo 和 undo 日志

-- 日志缓存大小
SHOW VARIABLES LIKE 'innodb_log_buffer_size';
​
-- 事务提交是日志刷新策略
-- 0:固定间隔, 1 秒执行一次。
-- 1:事务提交时,立刻写日志文件和刷新到磁盘,因为操作系统的“延迟写”特性,这里必须执行同步操作,才能保证事务提交成功的同时日志持久化到文件。
-- 2:事务提交时,立刻写日志文件,只是写入系统缓存,可能 1 秒后才会刷新到磁盘。
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';

2.3.4 额外的内存池

  • 每个缓冲池(innodb_buffer_pool)的帧缓冲(frame buffer)和缓冲控制对象(buffer control block)的内存需要从额外内存池中申请。

  • 帧缓冲和缓冲控制对象记录了一些诸如 LRU、等待、锁等信息。

  • 当申请的 InnoDB 缓冲池很大时,也要考虑适当地增加这个值。

2.4 InnoDB 后台线程

1) Master Thread

Master Thread 是 InnoDB 的核心主线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性。

包含:脏页的刷新(page cleaner thread)、合并插入缓冲(INSERT BUFFER)、undo 页的回收(purge thread)、redo 日志刷新(log thread)等。

Master Thread 具有最高的线程优先级别。内部由多个循环组成:

  • 主循环(loop)

  • 后台循环(backgroup loop)

  • 刷新循环(flush loop)

  • 暂停循环(suspend loop)

循环通过 thread sleep 来实现,负载很大的时候可能会有延迟(delay)。

InnoDB 1.0.x 版本之前的 Master Thread

  • 主循环(loop)—— 两大操作(每秒一次 & 每 10 秒一次)

    • 每秒一次

      • 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是);

      • 合并插入缓冲,IO 压力小才会执行(当前一秒发生的 IO 次数小于 5 次);

      • 如果当前缓冲池中脏页的比例(buf_get_modified_ratio_pct)超过了配置文件中 innodb_max_dirty_pages_pct 这个参数(默认为 90,代表 90%)时,InnoDB 判定为需要磁盘同步,会将 100 个脏页写入磁盘中;

      • 如果当前没有用户活动,则切换到后台循环(backgroup loop)。

    • 每 10 秒一次

      • 如果过去 10 秒之内磁盘的 IO 操作小于 200 次,InnoDB 认为磁盘 IO 压力小,会将 100 个脏页刷新到磁盘;

      • 合并至多 4 个插入缓冲(总是);

      • 将日志缓冲刷新到磁盘(总是);

      • 删除无用的 Undo 页(总是);

      • 如果脏页的比例(buf_get_modified_ratio_pct)大于 70%,则刷新 100 个脏页到磁盘;反之,刷新 10% 的脏页到磁盘。

  • 后台循环(backgroup loop)—— 数据库空闲(没有用户活动)或关闭(shutdown)时,会切换到这个循环

    • 删除无用的 Undo 页(总是);

    • 合并 20 个插入缓冲(总是);

    • 跳回到主循环(总是);

    • 不断刷新 100 个页直到符合条件(跳转到 flush loop 中完成)。

  • 刷新循环(flush loop)

    • 脏页刷新到磁盘

  • 暂停循环(suspend loop)

    • flush loop 中没有事情可以做了就会切换到这里,将 Master Thread 挂起,等待事件发生。

InnoDB 1.2.x 版本之前的 Master Thread

主要是对 1.0.x 版本之前的一些常量变成可配置参数,以适应实际的硬件性能

参数含义
innodb_io_capacity表示磁盘 IO 的吞吐量,默认值:200。 合并插入缓冲数量为 innodb_io_capacity 的 5%; 刷新脏页时,脏页数量为 innodb_io_capacity
innodb_max_dirty_pages_pct默认值从 92 调整为 75,脏页比例超过 75% 就同步到磁盘。
innodb_adaptive_flushing自适应刷新,原本脏页刷新固定是 100 个;现在会根据产生 redo log 的速度来决定最合适的刷新脏页数量。
innodb_purge_batch_size执行 full purge 是,原本最多回收 20 个 undo 页;现在按这个参数值回收,默认值:20。

InnoDB 1.2.x 版本的 Master Thread

原本每 10 秒一次的操作,调整为 InnoDB 空闲时才做;

刷新脏页的操作,从 Master Thread 线程分离到一个单独的 Page Cleaner Thread 中做;

减轻了 Master Thread 的工作,同时进一步提高了系统的并发性。

2) IO Thread

在 InnoDB 存储引擎中,大量使用了 AIO 来处理写请求,极大提高数据库的性能。

IO Thread 主要负责这些 IO 请求的回调处理。

SHOW VARIABLES LIKE 'innodb_%io_threads';
线程线程个数作用
read thread4将数据从磁盘加载到缓存 page
write thread4将缓存脏页刷新到磁盘
insert buffer thread1将写缓冲区内容刷新到磁盘
log thread1将日志缓冲区内容刷新到磁盘

3) Purge Thread

事务被提交后,其所使用的 undolog 可能不再需要,PurgeThread 用来回收已经使用并分配的 undo 页。

PurgeThread 是 InnoDB 1.1 版本引入的,减轻了 Master Thread 的工作,提高 CPU 的使用率以及存储引擎的性能。

  • 查看 PurgeThread 线程数

mysql> SHOW VARIABLES LIKE 'innodb_purge_threads';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| innodb_purge_threads | 4     |
+----------------------+-------+
  • 设置 PurgeThread 线程数【配置文件】

[mysqld]
innodb_purge_threads=4

4) Page Cleaner Thread

InnoDB 1.2.x 引入的,将脏页的刷新操作都交给这个线程来完成,减轻了原 Master Thread 的工作及对于用户查询线程的阻塞。

mysql> SHOW VARIABLES LIKE '%innodb_page_cleaners%';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| innodb_page_cleaners | 1     |
+----------------------+-------+

2.5 InnoDB 核心特性

2.5.1 Change Buffer(提升存储性能)

TIPS

  • 聚集索引(Primary Key)

  • 非聚集索引(secondary index),又称非主键索引、辅助索引、二级索引

  • 非聚集索引叶子节点的插入不是顺序的,需要离散地访问非聚集索引页,性能较差;

  • 对于 非聚集、非唯一 的辅助索引,更新操作不会直接写入索引页;

  • 而是先放入 Change Buffer 中,然后以一定的频率和情况进行 Change Buffer 和辅助索引页子节点的 merge(合并)操作;

  • 通常会将多个操作合并成一个(因为在一个索引页中),大大提高了非聚集索引的写性能;

唯一索引为什么不适用 Change Buffer?

唯一索引写入时,需要做唯一性校验,就必须离散地访问索引页(Change Buffer 就没有意义了)。

​参数配置(非常重要)

## change buffer 默认占缓冲池的 25%,最多可调整至 50%
mysql> SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| innodb_change_buffer_max_size | 25    |
+-------------------------------+-------+
​
## 开启 buffer 的选项:inserts、deletes、purges、changes、all、none
mysql> SHOW VARIABLES LIKE 'innodb_change_buffering';

以 UPDATE 为例,更新操作分为两个过程:

  • 将记录标记为已删除(对应 Delete Buffer)

  • 真正将记录删除(对应 Purge Buffer)

以 INSERT 为例,插入操作分为两个过程:

  • 将记录标记为插入(对应 Insert Buffer)

  • 真正将记录插入(对应 Buffer Pool)

2.5.2 doublewrite(两次写)

当 Page 发生部分写失败(partial page write)时,用来还原 Page 的副本。

数据库发生宕机时,可以通过 redo log 进行恢复。

如果宕机发生时,InnoDB 正在将某个页写到表中(比如 16KB 的页,只写了 4KB),那就要先通过【页的副本】还原这个页,再进行重做。

【页的副本】就存在 doublewrite 中,doublewrite 由两部分组成:

  • 内存中的 doublewrite buffer(2MB)

  • 物理磁盘上共享表空间中连续的 128 个页(64*2),一个页 16KB,128 个页正好 2MB

缓冲池的脏页进行刷新时:

  • 会将脏页先复制到内存的 doublewrite buffer

  • 通过 doublewrite buffer 分两次,每次 1 MB 顺序地写入共享表空间的物理磁盘上

  • 然后马上调用 fsync 函数,同步到磁盘【假设一个页还没写完,突然宕机】

  • 重启 MySQL ,从共享表空间中的 doublewirte 找到【页的副本】,将其复制到表空间文件

  • redo log 恢复丢失数据

211017 14:54:30  mysqld restarted
211017 14:54:31  InnoDB: Database was not shutdown normylly!
InnoDB: Starting crash recovery.
InnoDB: Reading tablespace information from the .ibd files...
InnoDB: Crash recovery may have failed for some .ibd files!
InnoDB: Restoring possible half-written data pages from the doublewrite
InnoDB: buffer...

2.5.3 自适应哈希索引

自适应哈希索引(Adaptive Hash Index, AHI):

InnoDB 存储引擎会监控对表上各索引页的查询,根据访问频率和模式来自动地为某些热点页建立哈希索引。

-- 查看自适应哈希索引是否开启
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index';

比如,连续 100 次以同样的模式(查询条件)访问同一个页,就会建立 AHI;不一定是 100 次,达到页中记录数的 1/16 即可。

三、其他存储引擎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值