Mysql--架构篇--存储引擎InnoDB(内存结构,磁盘结构,存储结构,日志管理,锁机制,事务并发控制等)

MySQL是一个多存储引擎的数据库管理系统,支持多种不同的存储引擎。每种存储引擎都有其独特的特性、优势和适用场景。选择合适的存储引擎对于优化数据库性能、确保数据完整性和满足业务需求至关重要。

注:在同一个Mysql的数据库中,对于不同的表,是可以指定不同存储引擎的。

一、MEMORY

概述:

  • 内存表:Memory引擎将所有数据存储在内存中,速度非常快,但不具备持久性。一旦服务器重启或断电,表中的数据将丢失。
  • 表级锁:Memory引擎使用表级锁,类似于MyISAM,但由于其在内存中执行,因此性能更高。
  • 固定长度记录:Memory引擎只支持固定长度的记录,不支持TEXT、BLOB等变长类型字段。
  • 临时表:Memory引擎常用于创建临时表,适合短期存储和快速查询。

适用场景:

  • 临时数据存储:Memory引擎适合用于存储临时数据,如会话信息、缓存数据等,尤其适用于需要快速查询和插入的场景。
  • 中间结果集:在复杂的查询中,可以将中间结果集存储在Memory表中,以提高查询性能。
  • 高速缓存:Memory引擎可以用作高速缓存,存储频繁访问的数据,减少对磁盘的读取。

二、MyISAM

概述:

  • 早期默认存储引擎:在MySQL 5.1及之前的版本中,MyISAM是默认的存储引擎。
  • 表级锁:MyISAM使用表级锁,当一个事务对表进行写操作时,整个表会被锁定,其他事务无法同时进行写操作,这可能导致性能瓶颈。
  • 不支持事务:MyISAM不支持事务,也没有回滚功能,因此不适合需要事务支持的应用场景。
  • 全文索引:MyISAM支持全文索引(Full-Text Index),适用于需要进行全文搜索的场景。
  • 压缩表:MyISAM支持创建压缩表,可以减少磁盘空间占用。

适用场景:

  • 读多写少:MyISAM的表级锁机制使得它在读多写少的场景下表现较好,尤其是当写操作较少且不会频繁发生时。
  • 全文搜索:MyISAM的全文索引功能使其适合需要进行全文搜索的应用,如博客、论坛等。
  • 日志记录:由于MyISAM的简单性和高效性,它适合用于日志记录、统计分析等场景,尤其是在不需要事务支持的情况下。

三、InnoDB

1、概述

  • 默认存储引擎:从MySQL 5.5版本开始,InnoDB成为MySQL的默认存储引擎。
  • 事务支持:InnoDB支持ACID(原子性、一致性、隔离性、持久性)事务,确保数据的完整性和可靠性。
  • 行级锁:InnoDB使用行级锁,允许多个事务并发访问不同行的数据,减少了锁冲突,适合高并发读写场景。
  • 外键约束:InnoDB支持外键约束,确保表与表之间的关系完整性。
  • 崩溃恢复:InnoDB具有自动崩溃恢复功能,能够在系统崩溃后快速恢复数据。
  • MVCC(多版本并发控制):InnoDB实现了MVCC,允许读操作不加锁,同时保证事务的隔离性,提升了并发性能。

适用场景:

  • 高并发读写:InnoDB的行级锁和MVCC机制使其非常适合高并发的读写操作。
  • 事务处理:InnoDB是唯一支持完整事务的存储引擎,适用于需要事务支持的应用程序,如银行系统、电子商务平台等。
  • 数据完整性要求高:由于支持外键约束和崩溃恢复,InnoDB适合对数据完整性有严格要求的场景。

性能优化:

  • 索引优化:InnoDB使用聚簇索引(Clustered Index),主键索引和数据存储在一起,可以提高查询性能。建议为表设置合理的主键。
  • 缓冲池:InnoDB有一个名为InnoDB Buffer Pool的内存区域,用于缓存数据和索引。可以通过调整innodb_buffer_pool_size参数来优化性能。
  • 日志文件:InnoDB使用重做日志(Redo Log)和回滚段(Undo Log)来实现事务的持久性和回滚。可以通过调整innodb_log_file_size和innodb_flush_log_at_trx_commit参数来优化日志性能。

2、InnoDB内部结构

下图是官方文档给出的架构图,可以看到InnoDB的架构主要分为两部分,一部分是内存结构,另一部分是磁盘结构。

(1)、内存结构

InnoDB内存结构主要分为Buffer Pool、Change Buffer、Adaptive Hash Index、Log Buffer四个部分。

1、缓冲池(Buffer Pool)
(1)、缓冲池概述

缓冲池 Buffer Pool,是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘中,从而减少磁盘IO,加快处理速度。

Buffer Pool的实现其实是一个链表,链表上为访问的页数据。

  • 功能:缓冲池是InnoDB的核心内存结构,用于缓存数据页和索引页。它显著减少了磁盘I/O操作,提升了读写性能。
  • 大小:缓冲池的大小由配置参数innodb_buffer_pool_size控制,通常建议设置为服务器物理内存的70%-80%。
  • 分片:可以通过innodb_buffer_pool_instances参数将缓冲池划分为多个实例,减少锁竞争,提升并发性能。
  • 基本单位:缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:
    • free page:空闲page,即:还未被使用的页。
    • clean page:被使用page,即:数据从磁盘加载到内存中的页,数据没有被修改过,此时内存页数据和磁盘页数据相同。
    • dirty page:脏页,被使用page,即:内存中的页数据被修改过,此时与磁盘的数据已经不一致了。
      在专用服务器上,通常将多达80%的物理内存分配给缓冲池 。

查看缓冲池大小:

java 复制代码
show variables like 'innodb_buffer_pool_size'; 

运行结果:

(2)、LRU算法
  • LRU(Least Recently Used):缓冲池使用LRU算法来管理内存页。当缓冲池满时,最久未使用的页将被移出,为新的数据页腾出空间。
  • Young和Old列表:InnoDB将缓冲池分为两个列表:年轻列表(Young List)和老年列表(Old List)。年轻列表用于存储最近访问的页,老年列表用于存储较早访问的页。这种设计有助于提高缓存命中率。
(3)、预读机制
  • 预读:InnoDB支持预读机制,可以在一次I/O操作中读取多个相邻的数据页,减少磁盘I/O次数。
    预读分为两种模式:
    • 线性预读:当顺序扫描表时,InnoDB会预读后续的页。
    • 随机预读:当频繁访问某些非连续的页时,InnoDB会预读这些页周围的页。
(4)、脏页刷新

当缓冲池中的页被修改后,它们被称为脏页。脏页不会立即写入磁盘,而是会在适当的时机进行刷新。

InnoDB使用以下机制来管理脏页刷新:

  • 后台刷新:InnoDB有一个后台线程定期将脏页刷新到磁盘。这个过程称为后台刷新(Background Flush)。
  • 检查点机制:InnoDB使用检查点(Checkpoint)来确保脏页及时写入磁盘。检查点是指重做日志(Redo Log)的某个位置,所有在此之前修改的页都必须刷新到磁盘。通过这种方式,InnoDB可以确保在系统崩溃后能够快速恢复数据。

理解:

Mysql的数据会被先保存到内存中,之后在保存到磁盘中。数据加载也是先从磁盘加载到内存中,在从内存提取返回给客户端。

在内存中的数据即使被修改了,也会先将修改后的结果保存到内存中,并不会立即刷新到磁盘中。这种内被修改了的页称之为脏页,InnoDB会定期将脏页的数据刷新到磁盘中。同时会将已经刷新的脏页的位置标记到Redo Log中,称之为检查点。下一次直接从这个检查点之后获取脏页数据在刷新到内存中即可。

(5)、分片缓冲池实例

为了减少锁争用,提升并发性能,InnoDB支持分片缓冲池(Buffer Pool Instances)。你可以通过innodb_buffer_pool_instances参数将缓冲池划分为多个实例。每个实例都有自己的锁和管理机制,从而减少了全局锁的争用。

2、更改缓冲区Change Buffer
(1)、更改缓冲区概述

更改缓冲区(Change Buffer)是InnoDB用于优化非聚簇索引(Secondary Index)DML语句操作的一种机制。在执行DML语句时(如:比如INSERT、UPDATE、DELETE),如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更信息暂存到更改缓冲区Change Buffer中,在未来数据被读取时,再将数据合并恢复到Buffer Pool中,最终合并后的数据在刷新到磁盘中。

Change Buffer的意义在于,不用每一次DML后都直接操作磁盘,造成大量的磁盘I/O。有了 Change Buffer之后,我们可以在缓冲池中进行合并处理,之后对批量数据进行一次I/O,减少磁盘I/O,提升写性能,尤其是在高并发场景下。

(2)、Change Buffer和聚簇索引,非聚簇索引的关系理解

更改缓冲区(Change Buffer)主要用于优化非聚簇索引的插入、更新和删除操作。

示例:

假设你有一个 users 表,如下:

java 复制代码
CREATE TABLE users (
    id INT PRIMARY KEY,  -- 聚簇索引(主键)
    name VARCHAR(50),
    age INT,
    INDEX idx_name (name)  -- 非聚簇索引
);

1、新增数据

当你向表中插入一条新记录时,InnoDB 会执行以下操作:

  • 聚簇索引(主键)的插入:新记录会根据主键的顺序插入到聚簇索引中,并且该操作会立即写入磁盘,以确保数据的一致性和完整性。

  • 非聚簇索引的插入:如果表中也有非聚簇索引(例如,基于name列的索引),InnoDB还需要将新记录的name值插入到相应的非聚簇索引中。这个插入操作不会立即写入磁盘,而是会先进入更改缓冲区,直到需要访问该索引页时再合并到磁盘上。

sql:

java 复制代码
INSERT INTO users (id, name, age) VALUES (1, 'Alice', 30);

解释:

  • 聚簇索引:id = 1的记录会被插入到聚簇索引中,并且该操作会立即写入磁盘。
  • 非聚簇索引:name = 'Alice'的记录会被插入到idx_name索引中。这个插入操作不会立即写入磁盘,而是会先进入更改缓冲区,直到需要访问该索引页时再合并到磁盘上。

简单理解:
新增数据有3个部分:第一部分是聚簇索引立即写入到磁盘,因为它决定了数据在磁盘的位置。第二步,会立即将数据写入到该聚簇索引在磁盘的位置中。第三步,对非聚簇索引会暂存到Change Buffer中,以减少磁盘I/O。

2、更新数据

当你更新一条现有记录时,InnoDB会执行以下操作:

  • 聚簇索引(主键)的更新:如果更新的是主键(聚簇索引),InnoDB会立即修改聚簇索引中的相应记录,并将该修改立即写入磁盘,以确保数据的一致性和完整性。

  • 非聚簇索引的更新:如果更新的是非聚簇索引列(例如,name列),InnoDB会将旧值和新值以及非聚簇索引的更新信息保存到更改缓冲区中。这些信息会在适当的时机(如索引页被读取时或后台刷新时)合并到磁盘上的索引页中。

sql:

java 复制代码
UPDATE users SET name = 'Bob' WHERE id = 1;

解释:

  • 聚簇索引:id = 1的记录中主键id没有发生变化,因此不需要修改聚簇索引。
  • 非聚簇索引:name列的值从'Alice'变为'Bob',InnoDB会将旧值'Alice'和新值'Bob'以及非聚簇索引的更新信息暂存到更改缓冲区中,直到需要访问该索引页时再合并到磁盘上。

简单理解:
更新数据时,如果修改是聚簇索引,那么数据的物理存储位置就会发生了改变,所以要第一时间写入到磁盘中,保证数据的完整性。如果更新的只是非聚簇索引,那么数据的物理位置就没有发生改变。可以先将更新的数据信息和非聚簇索引信息暂存到更改缓冲区中,以减少磁盘I/O频率,等待合适时机在合并到磁盘中。

3、删除操作

当你删除一条记录时,InnoDB会执行以下操作:

  • 聚簇索引(主键)的删除:InnoDB会立即从聚簇索引中删除该记录,并将该删除操作立即写入磁盘,以确保数据的一致性和完整性。

  • 非聚簇索引的删除:如果表中有非聚簇索引(例如,基于name列的索引),InnoDB还需要从非聚簇索引中删除该记录的name值。这个删除操作不会立即写入磁盘,而是会先进入更改缓冲区,直到需要访问该索引页时再合并到磁盘上。

sql:

java 复制代码
DELETE FROM users WHERE id = 1;   // 主键方式删除
DELETE FROM users WHERE name = 'Alice';   // 非聚簇索引方式删除

解释:

  • 聚簇索引:id = 1的记录会被从聚簇索引中删除,并且该操作会立即写入磁盘。
  • 非聚簇索引:name = 'Alice'的记录会被从idx_name索引中删除。这个删除操作不会立即写入磁盘,而是会先进入更改缓冲区,直到需要访问该索引页时再合并到磁盘上。

简单理解:
如果删除的是聚簇索引,聚簇索引信息和物理数据位置的数据都会被立即删除,以保证数据的完整性。但是如果这行数据还有非聚簇索引,这个非聚簇索引的删除信息会暂存到更改缓冲区中,以减少磁盘I/O频率,等待合适时机在合并到磁盘中。

(3)、更新缓冲区工作原理
  • 修改缓存:当对非聚簇索引进行插入、更新或删除操作时,InnoDB不会立即修改磁盘上的索引页,而是将这些修改暂存在更改缓冲区中。

  • 合并操作:当InnoDB需要读取某个索引页时,它会检查更改缓冲区中是否有对该页的未应用的修改。如果有,InnoDB会将这些修改与索引页合并,然后将结果写入磁盘。这个过程称为合并(Merge)。

  • 后台刷新:即使没有读取操作,InnoDB也会定期将更改缓冲区中的修改合并到磁盘上的索引页中。这个过程由后台线程执行,称为后台刷新(Background Flush)。

  • 崩溃恢复:更改缓冲区中的修改并不是持久化的,因此在系统崩溃后,这些修改可能会丢失。为了确保数据的一致性,InnoDB会在崩溃恢复时重新应用重做日志(Redo Log)中的相关记录。

(4)、更新缓冲区的优势
  • 减少磁盘I/O:通过将对非聚簇索引的修改暂存到内存中,InnoDB可以减少频繁的磁盘写入操作,尤其是当索引页不在缓冲池中时。这可以显著提升写性能,特别是在高并发场景下。

  • 批量合并:更改缓冲区允许InnoDB将多个小的修改合并为一次大的写操作,减少了磁盘I/O次数。例如,多个插入操作可以合并为一次批量插入,多个删除操作可以合并为一次批量删除。

  • 提高写密集型应用的性能:对于写密集型应用,特别是那些频繁插入或更新非聚簇索引的应用,更改缓冲区可以显著减少磁盘I/O,提升整体性能。

  • 优化冷索引:对于那些不经常被查询的非聚簇索引,更改缓冲区可以推迟写入操作,直到索引页被读取时再进行合并。这可以减少不必要的磁盘写入,提升性能。

(5)、更改缓冲区的限制
  • 仅适用于非聚簇索引:更改缓冲区仅对非聚簇索引有效,对聚簇索引(即主键索引)无效。因此,更改缓冲区不能优化对主键的插入、更新或删除操作。

  • 不适用于唯一索引:更改缓冲区不支持唯一索引(Unique Index)。对于唯一索引,InnoDB 必须立即检查是否存在重复值,因此无法将修改缓存到更改缓冲区中。

  • 内存占用:更改缓冲区占用缓冲池的一部分内存。如果更改缓冲区过大,可能会影响其他数据和索引页的缓存,导致缓冲池命中率下降。因此,需要合理配置更改缓冲区的大小。

  • 崩溃恢复:更改缓冲区中的修改不是持久化的,因此在系统崩溃后,这些修改可能会丢失。为了确保数据的一致性,InnoDB会在崩溃恢复时重新应用重做日志(Redo Log)中的相关记录。

(6)、相关配置参数

InnoDB提供了几个配置参数来控制更改缓冲区的行为和性能。以下是常用的配置参数及其作用:
1、innodb_change_buffer_max_size

  • 功能:该参数控制更改缓冲区的最大大小,占缓冲池的比例。该参数的值范围为0到50,默认值为25,表示更改缓冲区最多可以占用缓冲池的25%。
  • 推荐值:如果你的应用程序有大量对非聚簇索引的插入、更新或删除操作,可以适当增加该参数的值。但要注意,过大的更改缓冲区可能会影响其他数据和索引页的缓存,导致缓冲池命中率下降。

2、innodb_change_buffering

  • 功能:该参数控制哪些类型的索引操作可以使用更改缓冲区。可选值包括:

    • all:所有类型的索引操作都可以使用更改缓冲区(默认值)。
    • inserts:只有插入操作可以使用更改缓冲区。
    • deletes:只有删除操作可以使用更改缓冲区。
    • changes:更新操作可以使用更改缓冲区。
    • none:禁用更改缓冲区,所有索引操作都会立即写入磁盘。
  • 推荐值:默认值all适用于大多数场景。如果你的应用主要进行插入操作,可以设置为inserts,以减少不必要的删除操作进入更改缓冲区。如果你的应用对索引的实时性要求较高,可以考虑设置为none,以确保所有修改立即写入磁盘。

3、innodb_buffer_pool_instances

  • 功能:该参数控制缓冲池的分片数量。更改缓冲区也受此参数影响,每个分片都有自己的更改缓冲区。通过增加分片数量,可以减少锁争用,提升并发性能。
  • 推荐值:建议根据服务器的CPU核心数设置,通常设置为8或16。对于多核CPU的服务器,增加分片数量可以显著提升并发性能。
(7)、更改缓冲区的监控与优化

为了更好地管理和优化更改缓冲区,InnoDB提供了一些监控工具和性能指标。

1、performance_schema

从MySQL 5.7开始,performance_schema提供了更详细的监控信息。你可以查询 performance_schema.table_io_waits_summary_by_index_usage表,查看各个索引的I/O等待情况,从而评估更改缓冲区的效果。

sql:

java 复制代码
SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage 
WHERE index_name IS NOT NULL;

运行结果:

2、INFORMATION_SCHEMA.INNODB_BUFFER_POOL_STATS

该表提供了缓冲池的统计信息,包括更改缓冲区的使用情况。你可以查询该表,查看更改缓冲区的命中率、合并次数等指标。

sql:

java 复制代码
SELECT * FROM INFORMATION_SCHEMA.INNODB_BUFFER_POOL_STATS;

运行结果:

3、Adaptive Hash Index

自适应hash索引,用于优化对Buffer Pool数据的查询。MySQL的innoDB引擎中虽然没有直接支持hash索引,但是给我们提供了一个功能就是这个自适应hash索引。

hash索引在进行等值匹配时,一般性能是要高于B+树的,因为hash索引一般只需要一次I/O即可,而B+树,可能需要几次匹配,所以hash索引的效率要高,但是hash索引又不适合做范围查询、模糊匹配等。

InnoDB存储引擎会监控表上各索引页的查询,如果观察到在特定的条件下hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。

自适应哈希索引,无需人工干预,是InnoDB根据情况自动完成的。

示例:

java 复制代码
show VARIABLES like '%adaptive_hash_index%';

运行结果:

4、Log Buffer

Log Buffer(日志缓冲区):用来保存要写入到磁盘中的log日志数据的缓存部分(如:redo log 、undo log等),默认大小为16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘 I/O。

查看日志缓冲区大小:

java 复制代码
show VARIABLES like '%innodb_log_buffer_size%';

运行结果:

查看日志刷新到磁盘的时机:

java 复制代码
show VARIABLES like '%innodb_flush_log_at_trx_commit%';

解释:

1: 日志在每次事务提交时写入并刷新到磁盘,默认值。

0: 每秒将日志写入并刷新到磁盘一次。

2: 日志在每次事务提交后写入,并每秒刷新到磁盘一次。
运行结果:

(2)、磁盘结构

InnoDB磁盘结构主要包含表、索引、表空间、Doublwrite Buffer、Redo Log和Undo Log几个部分。

1、表空间(Tablespace)

表空间是InnoDB存储数据和索引的基本单位。每个表空间由一个或多个文件组成,用于存储表的数据页和索引页。根据配置,表空间可以分为以下几类:

(1)、系统表空间(System Tablespace)

针对Double write buffer和Change buffer的一块物理存储区域。如果表建立在系统表空间内,那么也会包含表和索引的数据。

它是InnoDB的全局共享表空间,系统表空间通常位于ibdata1文件中。

功能:

  • 数据字典:存储所有表的元数据,包括表结构、索引信息等。
  • 回滚段:存储回滚段(Undo Log),用于实现事务的回滚和多版本并发控制(MVCC)。
  • 双写缓冲区:存储双写缓冲区(Doublewrite Buffer),用于防止页面损坏。
  • 插入缓冲区:存储插入缓冲区(Insert Buffer),用于加速非聚簇索引的插入操作。
(2)、独立表空间(File-Per-Table Tablespace)

过去InnoDB都是把表数据存储在系统表空间内,这种方式适用于专门用于数据库处理的机器,而File-Per-Table Tablespace允许每个表的数据都可以存储在自己的表空间数据文件里(.ibd文件)。

这是通过配置参数innodb_file_per_table = ON实现的。启用后,每个表的数据和索引都会存储在单独的.ibd文件中。在较新版本的Mysql中,都是默认开启独立标间存储的。

sql:

java 复制代码
show VARIABLES like '%innodb_file_per_table%';

运行结果:

优点:

  • 方便管理:可以单独对某个表进行备份、恢复、收缩或删除。
  • 减少碎片:独立表空间可以减少全局表空间的碎片化问题。
  • 支持在线DDL操作:某些DDL操作(如添加索引)可以在不影响其他表的情况下执行。
(3)、临时表空间(Temporary Tablespace)

临时表空间用于存储临时表和内部临时表(如排序、分组等操作)。临时表空间通常位于ibtmp1文件中。

功能:

  • 存储临时表的数据和索引。
  • 存储内部临时表,用于查询优化(如排序、分组、连接等)。
(4)、通用表空间(General Tablespace)

通用表空间允许用户自定义创建多个共享表空间,可以为特定的表集分配一个独立的表空间文件。可以通过CREATE TABLESPACE语法创建共享表空间。

上面介绍的3种表空间都是系统控制的,有自己独立的业务场景需要。如果我们自己想要独立的表空间的话,可以在通用表空间中自己创建出来再使用到具体的表上。

优点:

  • 灵活管理:可以根据需要创建多个通用表空间,便于管理和优化。
  • 支持大文件:通用表空间可以跨越多个文件,适用于存储非常大的表。
(5)、撤销表空间(Undo Tablespace)

撤销表空间,MySQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16M),用于存储undo log日志。 通过该日志可以撤销事务对聚簇索引数据的最新修改,即我们常说的数据回滚。

2、双写缓冲区(Doublewrite Buffer Files)

双写缓冲区(Doublewrite Buffer Files)是InnoDB用于防止页面损坏的关键机制。InnoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。

双写缓冲区存储在系统表空间中,包含128个16KB的页。

工作原理:

  • InnoDB将要写入的数据页分成16KB的块。
  • 这些块首先被写入双写缓冲区。
  • 然后,InnoDB将这些块写入实际的数据表空间文件中。
  • 如果系统崩溃,InnoDB可以根据双写缓冲区中的数据页快速恢复损坏的页。

功能:

  • 防止页面损坏:在将数据页写入磁盘之前,InnoDB会先将数据页的副本写入双写缓冲区。如果系统崩溃,InnoDB可以从双写缓冲区中恢复损坏的数据页。
  • 简化恢复过程:在崩溃恢复时,InnoDB只需要检查双写缓冲区中的页,而不需要逐个检查所有数据页,从而加快恢复速度。
3、Redo Log

重做日志(Redo log)是InnoDB用于确保事务持久性和崩溃恢复的关键组件。

该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中, 用于发生错误时, 进行数据恢复使用。

重做日志文件通常位于ib_logfile0和ib_logfile1中,文件大小由innodb_log_file_size参数控制。

功能:

  • 记录所有对数据页的修改操作,确保事务的持久性。
  • 在系统崩溃后,InnoDB可以根据重做日志重新应用未完成的事务,恢复数据的一致性。
  • 重做日志采用循环写入的方式,当一个日志文件写满后,InnoDB会切换到下一个日志文件。

3、InnoDB存储结构

InnoDB数据存储结构示例图:

(1)、表空间(Tablespace)

表空间是InnoDB存储数据和索引的基本单位。一个mysql实例可以对应多个表空间,每个表空间由一个或多个文件组成,用于存储表的数据页和索引页。

表空间可以分为5类:

系统表空间(System Tablespace),即:ibdata1文件。

独立表空间(File-Per-Table Tablespace),即:每一个.ibd文件。

临时表空间(Temporary Tablespace),常位于ibtmp1文件中。

撤销表空间(Undo Tablespace),用于存储undo log日志,名称为:undo_001类似的文件。

通用表空间(General Tablespace),用于自定义创建的表空间。

(2)、段(Segment)

段(Segment)是表空间中的逻辑分区,用于管理不同类型的对象。InnoDB中的段主要用于管理数据段和索引段。
简单说:一个表空间是由多个逻辑段组成的。

按照功能类型划分,段的种类分为如下3种:
1、数据段(Data Segment)

数据段用于存储表的实际数据行。每个表都有一个数据段,负责管理数据页的分配和回收。

特点:

  • 按需分配:数据段会根据需要动态分配新的数据页。
  • 支持扩展:当表的数据量增加时,数据段可以自动扩展,分配更多的数据页。

2、索引段(Index Segment)

索引段用于存储表的索引信息。每个索引(包括聚簇索引和非聚簇索引)都有一个独立的索引段,负责管理索引页的分配和回收。

特点:

  • 按需分配:索引段会根据需要动态分配新的索引页。
  • 支持扩展:当索引的大小增加时,索引段可以自动扩展,分配更多的索引页。

3、回滚段(Undo Segment)

回滚段用于存储回滚日志(Undo Log),记录了事务的旧版本数据。回滚段是InnoDB实现事务的回滚和多版本并发控制(MVCC)的关键组件。

特点:

  • 按需分配:回滚段会根据需要动态分配新的回滚页。
  • 支持扩展:当事务的回滚日志增多时,回滚段可以自动扩展,分配更多的回滚页。
(3)、区(Extent)

区是InnoDB中的一个物理存储单元,每个区由64个连续的页组成。区是InnoDB分配和回收存储空间的基本单位。

每个区包含64个页,默认情况下每个页的大小为16KB,因此每个区的大小为1MB(64 * 16KB)。

功能:

  • 分配存储空间:InnoDB通过分配区来为数据段、索引段和回滚段分配存储空间。
  • 回收存储空间:当数据或索引被删除时,InnoDB会将空闲的区标记为可回收,并在适当的时候将其回收。
(4)、页(Page)

页是InnoDB中最小的I/O单位,所有的数据和索引都存储在页中。页是InnoDB进行读取、写入和缓存的基本单位。

InnoDB的默认页大小为16KB,可以通过innodb_page_size参数调整为4KB或8KB。

影响:

页大小的选择会影响I/O性能和内存使用。较大的页可以减少磁盘I/O次数,但会增加内存占用;较小的页可以提高内存利用率,但可能会增加 I/O 次数。

页类型:

InnoDB中有多种类型的页,每种页用于存储不同类型的数据:

  • 数据页(Data Page):存储表的实际数据行。
  • 索引页(Index Page):存储索引信息。
  • undo 页:存储回滚段数据。
  • 系统页:存储元数据和其他控制信息。
  • 自由页(Free Page):未使用的空闲页,等待分配。
  • 插入缓冲区页(Insert Buffer Page):用于缓存对非聚簇索引的插入操作。
  • 更改缓冲区页(Change Buffer Page):用于缓存对非聚簇索引的插入、更新和删除操作。

页结构:

每个页都有固定的结构,主要包括以下几个部分:

  • 页头(Page Header):包含页的元数据,如页号、页类型、自由空间指针等。
  • 用户数据(User Data):存储实际的数据或索引信息。
  • 页尾(Page Trailer):包含校验和和LSN(日志序列号),用于检测页面是否损坏。
(5)、行(Row)

行(Row),InnoDB 存储引擎数据是按行进行存放的。在行中,默认有两个隐藏字段:

Trx_id:每次对某条记录进行改动时,都会把对应的事务id 赋值给trx_id隐藏列。

Roll_pointer:每次对某条引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个回滚指针,可以通过它来找到该记录修改前的信息。

4、InnoDB日志管理

InnoDB的日志管理是其事务处理和崩溃恢复机制的核心组成部分。通过日志,InnoDB能够确保数据的持久性和一致性,即使在系统崩溃的情况下也能快速恢复到一致的状态。InnoDB主要使用两种类型的日志:重做日志(Redo Log)和回滚段(Undo Log)。此外,MySQL还提供了二进制日志(Binary Log)用于主从复制和备份恢复。

(1)、重做日志(Redo Log)
1、概述

重做日志是InnoDB用于确保事务持久性的关键组件。它记录了所有对数据页的物理修改操作,确保在系统崩溃后可以重新应用这些修改,恢复数据的一致性。

  • 位置:重做日志文件通常位于ib_logfile0和ib_logfile1中。
  • 大小:每个重做日志文件的大小由innodb_log_file_size参数控制,默认值为48MB。
  • 数量:可以通过innodb_log_files_in_group参数指定重做日志文件的数量,默认为2。
2、工作原理

(1)、记录修改:每当对数据页进行修改时,InnoDB会先将修改操作记录到重做日志中。重做日志记录的是物理修改,而不是逻辑操作。即:记录了页的相关变化,而不是具体的SQL语句。

(2)、循环写入:重做日志采用循环写入的方式。当一个日志文件写满后,InnoDB会切换到下一个日志文件继续写入。所有日志文件写满后,InnoDB会回到第一个日志文件,覆盖旧的日志记录。

(3)、检查点(Checkpoint):为了防止重做日志被无限期地循环覆盖,InnoDB使用检查点机制。检查点是指重做日志的某个位置,所有在此之前修改的数据页都必须已经刷新到磁盘。通过这种方式,InnoDB可以确保在系统崩溃后能够从检查点开始恢复数据。

(4)、崩溃恢复:在系统崩溃后,InnoDB会在启动时读取重做日志,重新应用未完成的事务,恢复数据的一致性。这个过程称为前滚恢复(Roll Forward Recovery)。

3、配置参数

innodb_log_file_size:

  • 功能:设置每个重做日志文件的大小。
  • 推荐值:建议根据工作负载调整。对于高并发写入场景,可以适当增大该值,以减少日志切换的频率。默认值为48MB。

innodb_log_files_in_group:

  • 功能:设置重做日志文件的数量。
  • 推荐值:默认为2,通常不需要调整。如果需要更大的日志空间,建议增加单个日志文件的大小,而不是增加日志文件的数量。

innodb_flush_log_at_trx_commit:

  • 功能:控制事务提交时是否立即刷新重做日志到磁盘。
  • 取值:
    • 0:不刷新重做日志,性能最好,但安全性最低。如果系统崩溃,可能会丢失最近的事务。
    • 1(默认):每次事务提交时都刷新重做日志到磁盘,确保数据的安全性,但性能稍差。
    • 2:每次事务提交时将重做日志写入操作系统缓存,但不立即刷新到磁盘。适合追求性能的场景,但在系统崩溃时可能会丢失最近的事务。

innodb_log_buffer_size:

  • 功能:设置重做日志缓冲区的大小。重做日志缓冲区用于暂存尚未写入磁盘的重做日志记录。
  • 推荐值:默认为8MB,可以根据工作负载适当调整。较大的缓冲区可以减少磁盘I/O次数,提升写性能。
4、优化建议
  • 增大innodb_log_file_size:对于高并发写入场景,建议增大重做日志文件的大小,以减少日志切换的频率。较大的日志文件可以容纳更多的事务,减少磁盘I/O次数。

  • 调整innodb_flush_log_at_trx_commit:根据应用场景选择合适的值。如果你的应用对数据安全要求较高,建议保持默认值1;如果你的应用对性能要求较高,且可以容忍少量数据丢失,可以选择2或0。

  • 使用O_DIRECT刷新方法:通过innodb_flush_method = O_DIRECT配置,绕过操作系统的缓存,直接将重做日志写入磁盘,减少双重缓存问题,提升性能。

(2)、回滚日志(Undo Log)
1、概述

回滚日志是InnoDB用于实现事务的回滚和多版本并发控制(MVCC)的关键组件。它记录了事务的旧版本数据,确保未提交的事务不会影响数据库的状态,并支持多个事务同时读取不同的数据版本。

  • 位置:回滚段存储在系统表空间中,通常是ibdata1文件的一部分。
  • 类型:
  • 插入回滚段:用于记录插入操作的旧版本数据。
  • 更新回滚段:用于记录更新操作的旧版本数据。
2、工作原理

(1)、记录旧版本数据:每当对数据进行插入、更新或删除操作时,InnoDB会将旧版本数据记录到回滚日志中。这些旧版本数据用于实现事务的回滚和多版本并发控制(MVCC)。

(2)、事务回滚:如果事务未提交或发生错误,InnoDB可以根据回滚段中的旧版本数据,将数据恢复到事务开始时的状态。

(3)、多版本并发控制(MVCC):InnoDB使用回滚段来支持多版本并发控制。当多个事务同时读取同一行数据时,InnoDB会根据事务的隔离级别返回合适的数据版本。例如,在读已提交(Read Committed)隔离级别下,事务只能看到已经提交的数据;而在可重复读(Repeatable Read)隔离级别下,事务在整个生命周期内都能看到相同的数据版本。

(4)、清理旧版本数据:当事务提交后,回滚段中的旧版本数据不再需要,InnoDB会定期清理这些数据,释放空间。

3、配置参数

innodb_undo_tablespaces:

  • 功能:设置独立的回滚表空间的数量。启用该参数后,回滚段将存储在独立的.ibd文件中,而不是系统表空间中。
  • 推荐值:默认为0,表示回滚段存储在系统表空间中。如果你有大量长时间运行的事务,建议启用独立的回滚表空间,以减少系统表空间的碎片化问题。

innodb_undo_log_truncate:

  • 功能:控制是否定期截断回滚段。启用该参数后,InnoDB会定期清理不再需要的回滚段,释放空间。
  • 推荐值:默认为OFF,建议在生产环境中启用该参数,以减少回滚段的占用空间。

innodb_max_undo_log_size:

  • 功能:设置回滚段的最大大小。当回滚段超过该大小时,InnoDB会自动截断回滚段,释放空间。
  • 推荐值:默认为1GB,可以根据工作负载适当调整。
4、优化建议
  • 启用独立回滚表空间:如果你有大量长时间运行的事务,建议启用独立的回滚表空间,以减少系统表空间的碎片化问题。独立的回滚表空间可以更好地管理和优化回滚段的存储。

  • 定期截断回滚段:启用innodb_undo_log_truncate参数,定期清理不再需要的回滚段,释放空间。这有助于减少回滚段的占用空间,提升性能。

  • 调整回滚段大小:根据工作负载调整innodb_max_undo_log_size参数,确保回滚段不会占用过多的空间。对于大事务或长事务较多的场景,可以适当增大该值。

(3)、二进制日志(Binary Log)
1、概述

二进制日志是MySQL用于主从复制和备份恢复的关键组件。它记录了所有对数据库的修改操作,包括DDL(数据定义语言)和DML(数据操作语言)语句。

  • 位置:二进制日志文件通常位于mysql-bin.000001等文件中。
  • 格式:二进制日志记录的是逻辑操作,而不是物理修改。每个日志文件包含一系列事件(Event),每个事件代表一个修改操作。
2、工作原理

(1)、记录修改:每当对数据库进行修改操作时,MySQL会将该操作记录到二进制日志中。二进制日志记录的是逻辑操作,例如INSERT、UPDATE、DELETE等语句。

(2)、主从复制:在主从复制架构中,主库会将二进制日志发送给从库,从库会根据这些日志重新执行相同的修改操作,确保主从库的数据一致。

(3)、备份恢复:二进制日志可以用于备份和恢复。通过二进制日志,可以在全量备份的基础上恢复到特定的时间点,确保数据的完整性。

3、配置参数

log_bin:

  • 功能:启用或禁用二进制日志。
  • 推荐值:如果你需要主从复制或备份恢复功能,建议启用二进制日志。默认情况下,二进制日志是禁用的。

binlog_format:

  • 功能:设置二进制日志的格式。
  • 取值:
    • STATEMENT:记录SQL语句本身,适用于简单的查询,但可能无法正确处理某些复杂的操作。
    • ROW:记录每一行的变化,适用于复杂查询和事务,但日志文件较大。
    • MIXED:根据情况自动选择STATEMENT或ROW模式,适用于大多数场景。

expire_logs_days:

  • 功能:设置二进制日志的保留天数。超过该天数的日志文件将被自动删除。
  • 推荐值:根据备份策略和磁盘空间调整。建议设置合理的保留时间,避免日志文件占用过多空间。

sync_binlog:

  • 功能:控制是否每次写入二进制日志后立即同步到磁盘。
  • 取值:
    • 0:不立即同步,性能最好,但安全性最低。如果系统崩溃,可能会丢失最近的日志记录。
    • 1(默认):每次写入后立即同步到磁盘,确保数据的安全性,但性能稍差。
    • N > 1:每N次写入后同步一次,平衡性能和安全性。
4、优化建议
  • 选择合适的日志格式:根据应用场景选择合适的二进制日志格式。对于简单的查询STATEMENT模式性能较好;对于复杂查询和事务,ROW模式更安全可靠;MIXED模式则适用于大多数场景。

  • 合理设置日志保留时间:根据备份策略和磁盘空间调整expire_logs_days参数,避免日志文件占用过多空间。建议设置合理的保留时间,确保在需要时可以恢复到特定的时间点。

  • 启用日志同步:根据应用程序对数据安全的要求,选择合适的sync_binlog值。如果你的应用对数据安全要求较高,建议保持默认值1;如果你的应用对性能要求较高,且可以容忍少量日志丢失,可以选择0或较大的值。

(4)、日志管理总结

InnoDB的日志管理是其事务处理和崩溃恢复机制的核心组成部分。通过重做日志、回滚段和二进制日志,InnoDB能够确保数据的持久性和一致性,即使在系统崩溃的情况下也能快速恢复到一致的状态。

  • 重做日志(Redo Log):用于确保事务的持久性,记录对数据页的物理修改操作。通过循环写入和检查点机制,InnoDB可以在系统崩溃后快速恢复数据。

  • 回滚段(Undo Log):用于实现事务的回滚和多版本并发控制(MVCC),记录事务的旧版本数据。通过独立回滚表空间和定期截断回滚段,可以优化回滚段的存储和性能。

  • 二进制日志(Binary Log):用于主从复制和备份恢复,记录对数据库的逻辑修改操作。通过合理的日志格式和保留时间设置,可以确保数据的安全性和可恢复性。

5、InnoDB锁机制

InnoDB 是 MySQL 的默认存储引擎,提供了强大的事务支持和并发控制机制。为了确保数据的一致性和并发性,InnoDB 使用了多种锁机制来管理对数据的访问。理解 InnoDB 的锁机制对于优化数据库性能、避免死锁以及确保事务的隔离性至关重要。

InnoDB的锁机制主要包括以下几种:

  • 行级锁(Row-Level Locking)
  • 表级锁(Table-Level Locking)
  • 意向锁(Intention Locks)
  • 间隙锁(Gap Locks)
  • 临键锁(Next-Key Locks)
  • 记录锁(Record Locks)
  • 自动增长锁(Auto-Increment Locks)
  • 元数据锁(Metadata Locks)
(1)、行级锁(Row-Level Locking)

行级锁是InnoDB最常用的锁类型,它只锁定被操作的特定行,而不是整个表。行级锁可以提高并发性,允许多个事务同时对不同行进行操作,而不会相互阻塞。

  • 优点:行级锁的粒度较小,能够有效减少锁冲突,提升并发性能。
  • 缺点:行级锁需要更多的内存和CPU资源来管理和维护锁信息,尤其是在高并发场景下可能会导致性能下降。

行级锁分类:

  • 记录锁(Record Locks):锁定单个索引记录。例如,在SELECT ... FOR UPDATE或UPDATE操作中,InnoDB会为涉及的每一行加记录锁。
  • 间隙锁(Gap Locks):锁定索引中的一个区间(即"间隙"),防止其他事务在这个区间内插入新行。间隙锁主要用于可重复读(Repeatable Read)隔离级别下的防幻读(Phantom Read)问题。
  • 临键锁(Next-Key Locks):结合了记录锁和间隙锁,锁定一个范围内的所有行和间隙。临键锁用于防止其他事务在该范围内插入新行或修改现有行。这是InnoDB在可重复读隔离级别下的默认锁机制。

应用场景:

  • SELECT ... FOR UPDATE:在读取数据时加写锁,防止其他事务修改或删除这些行。
  • SELECT ... LOCK IN SHARE MODE:在读取数据时加读锁,允许其他事务读取相同的数据,但不允许修改或删除。
  • INSERT、UPDATE、DELETE:在修改数据时自动加写锁,确保同一行不会被多个事务同时修改。
(2)、表级锁(Table-Level Locking)

表级锁是锁定整个表的锁,阻止其他事务对该表进行任何操作。表级锁的粒度较大,适用于某些特殊场景,但在大多数情况下,行级锁更为常用。

  • 优点:表级锁的实现简单,管理成本低,适合全表扫描或批量操作。
  • 缺点:表级锁会严重限制并发性,可能导致大量事务阻塞,影响性能。

工作原理:

  • LOCK TABLES:显式地对表加锁。可以通过READ或WRITE锁定表,分别表示只读锁和写锁。
  • UNLOCK TABLES:释放显式加的表锁。

应用场景:

  • 批量导入数据:在导入大量数据时,可以使用表级锁来防止其他事务对该表进行操作,确保数据一致性。
  • 全表扫描:在执行全表扫描时,可以使用表级锁来防止其他事务修改表中的数据。
(3)、意向锁(Intention Locks)

意向锁是一种解决行锁和表锁冲突的机制。其作用为表级别的锁,用于表明事务打算在表的某个范围内加行级锁。意向锁本身并不锁定任何行,而是作为一种信号,告诉其他事务当前事务可能会影响哪些行。

可以理解为意向锁实际上是一种解决锁冲突的机制,主要用于解决行锁和表级锁之间的兼容问题,并不是实质意义上的锁。

  • 意向共享锁(Intention Shared Lock, IS):表明事务打算在表的某些行上加共享锁(读锁)。
  • 意向排他锁(Intention Exclusive Lock, IX):表明事务打算在表的某些行上加排他锁(写锁)。

工作原理:

  • IS锁:当事务打算在表的某些行上加共享锁时,会先在表上加IS锁。IS锁与其他S锁兼容,但与X锁不兼容。
  • IX锁:当事务打算在表的某些行上加排他锁时,会先在表上加IX锁。IX锁与其他X锁不兼容,但与S锁兼容。
    注意下:
    意向锁主要是解决行锁和表锁之间的兼容问题。当一张表具有IX时,意向锁会阻止其他事务再次获取该表的X锁,而不是阻止其他事务获取表中某一行的X锁。即:对于同一个表,行锁和其他事务行锁还是可以共存的,只要行不一致就可以。但是行锁和表锁是不能共存的。

应用场景:

  • 并发控制:意向锁用于防止多个事务同时对同一张表的不同行加锁时发生冲突。通过意向锁,InnoDB可以提前检测到潜在的锁冲突,避免不必要的等待和死锁。
(4)、间隙锁(Gap Locks)

间隙锁是锁定索引中的一个区间(即"间隙"),防止其他事务在这个区间内插入新行。间隙锁主要用于可重复读(Repeatable Read)隔离级别下的防幻读(Phantom Read)问题。

简单理解:目标行索引的前后相邻索引区间的行都会被锁住,即锁住了周围的多行数据,而不仅仅只是锁住目标那一行的数据。

幻读问题:

在一个事务中,两次查询返回的结果集行数不同,因为另一个事务在这两次查询之间插入了新行。间隙锁可以防止这种情况发生。

工作原理:

  • 锁定区间:间隙锁锁定的是索引中的一个区间,而不是具体的行。例如,如果索引中有两个相邻的值1和3,间隙锁可以锁定(1, 3)这个区间,防止其他事务在此区间内插入新行。
  • 与记录锁结合:间隙锁通常与记录锁结合使用,形成临键锁(Next-Key Lock),锁定一个范围内的所有行和间隙。

应用场景:

  • 防幻读:在可重复读隔离级别下,间隙锁用于防止其他事务在查询结果集中插入新行,确保事务在整个生命周期内看到相同的数据版本。
  • 唯一约束:在插入新行时,InnoDB会自动加间隙锁,确保唯一约束不会被违反。
(5)、临键锁(Next-Key Locks)

临键锁是结合了记录锁和间隙锁的锁类型,锁定一个范围内的所有行和间隙。临键锁是 InnoDB在可重复读隔离级别下的默认锁机制,用于防止其他事务在该范围内插入新行或修改现有行。

  • 范围锁定:临键锁不仅锁定具体的行,还锁定行之间的间隙,防止其他事务在该范围内插入新行。

工作原理:

  • 锁定范围:临键锁锁定的是一个范围,包括索引中的具体行和行之间的间隙。例如,如果索引中有两个相邻的值1和3,临键锁可以锁定 [1, 3] 这个范围,防止其他事务在此范围内插入新行或修改现有行。
  • 防幻读:临键锁可以有效防止幻读问题,确保事务在整个生命周期内看到相同的数据版本。

应用场景:

  • 可重复读隔离级别:在可重复读隔离级别下,临键锁是默认的锁机制,用于防止其他事务在查询结果集中插入新行或修改现有行。
  • 唯一约束:在插入新行时,InnoDB会自动加临键锁,确保唯一约束不会被违反。
(6)、记录锁(Record Locks)

记录锁是锁定单个索引记录的锁,防止其他事务修改或删除该记录。记录锁是最常见的行级锁类型。

  • 锁定单行:记录锁只锁定具体的行,而不锁定行之间的间隙。

工作原理:

  • SELECT ... FOR UPDATE:在读取数据时加写锁,防止其他事务修改或删除这些行。
  • SELECT ... LOCK IN SHARE MODE:在读取数据时加读锁,允许其他事务读取相同的数据,但不允许修改或删除。
  • INSERT、UPDATE、DELETE:在修改数据时自动加写锁,确保同一行不会被多个事务同时修改。

应用场景:

  • 更新操作:在修改数据时,InnoDB会自动加记录锁,确保同一行不会被多个事务同时修改。
  • 读取操作:在读取数据时,可以根据需要显式加记录锁,确保数据的一致性。
(7)、自动增长锁(Auto-Increment Locks)

自动增长锁用于管理AUTO_INCREMENT列的值分配,确保多个事务不会生成重复的自增值。

  • 传统模式:在传统模式下,InnoDB会对整个表加表级锁,确保只有一个事务可以分配AUTO_INCREMENT值。这种方式虽然简单,但会严重限制并发性。
  • 互斥锁模式:从MySQL 5.1开始,InnoDB引入了互斥锁模式,允许多个事务并发分配AUTO_INCREMENT值,而不需要对整个表加锁。

配置参数:

innodb_autoinc_lock_mode:

  • 0(传统模式):对整个表加表级锁,确保只有一个事务可以分配AUTO_INCREMENT值。适用于需要严格顺序的场景,但并发性较差。
  • 1(连续模式):允许多个事务并发分配AUTO_INCREMENT值,但可能会出现跳号现象。适用于大多数场景,推荐使用。
  • 2(交错模式):允许多个事务并发分配AUTO_INCREMENT值,且不会出现跳号现象。适用于高并发场景,但可能会导致自增值不连续。

应用场景:

  • 高并发插入:在高并发插入场景下,建议使用innodb_autoinc_lock_mode = 1或2,以提高并发性能。
  • 严格顺序要求:如果应用对AUTO_INCREMENT值的顺序有严格要求,建议使用innodb_autoinc_lock_mode = 0,但要注意并发性较低。
(8)、元数据锁(Metadata Locks)

元数据锁用于管理对表结构的修改操作,确保在事务执行期间表结构不会发生变化。元数据锁可以防止多个事务同时对同一张表进行DDL操作,或者在事务执行期间对表结构进行修改。

  • 表结构保护:元数据锁确保在事务执行期间,表结构不会被修改,防止数据不一致问题。
  • DDL操作保护:元数据锁可以防止多个事务同时对同一张表进行DDL操作,确保表结构的一致性。

工作原理:

  • 读锁:当事务读取表时,InnoDB会为该表加读元数据锁,允许其他事务读取相同的数据,但不允许修改表结构。
  • 写锁:当事务修改表结构时,InnoDB会为该表加写元数据锁,阻止其他事务对该表进行任何读写操作,直到DDL操作完成。

应用场景:

  • DDL操作:在执行DDL操作(如ALTER TABLE)时,InnoDB会自动加写元数据锁,确保表结构的一致性。
  • 事务执行期间:在事务执行期间,InnoDB会为涉及的表加读元数据锁,防止其他事务修改表结构,确保数据的一致性。
(9)、死锁检测与处理
1、死锁检测

InnoDB自动检测死锁,并选择回滚其中一个事务来解决死锁问题。InnoDB会定期检查是否存在循环等待的情况来发现死锁。

  • 死锁检测频率:InnoDB会在每次等待锁时检查是否存在死锁。如果检测到死锁,InnoDB会选择回滚代价最小的事务。
  • 回滚策略:InnoDB会根据事务的大小、已修改的数据量等因素,选择回滚代价最小的事务,以减少对系统的影响。
2、死锁预防

虽然InnoDB提供了自动的死锁检测机制,但实际过程中还是建议避免频繁的死锁发生。

建议采取以下措施:

  • 尽量减少事务的持有时间:尽量缩短事务的持续时间,减少锁的持有时间,降低死锁发生的概率。
  • 按固定顺序加锁:在多个事务中,尽量按照相同的顺序加锁,避免交叉加锁导致的死锁。
  • 使用合适的隔离级别:根据应用场景选择合适的隔离级别,避免不必要的锁竞争。例如,在不需要严格一致性的场景下,可以选择读已提交(Read Committed)隔离级别,减少锁的使用。
(10)、锁机制总结

InnoDB的锁机制是其事务处理和并发控制的核心组成部分。通过行级锁、表级锁、意向锁、间隙锁、临键锁等多种锁类型,InnoDB能够有效地管理对数据的访问,确保数据的一致性和并发性。

  • 行级锁:提供细粒度的锁控制,允许多个事务同时对不同行进行操作,提升并发性能。
  • 表级锁:适用于全表扫描或批量操作,但在大多数情况下,行级锁更为常用。
  • 意向锁:用于表明事务打算在表的某些行上加锁,帮助提前检测潜在的锁冲突。
  • 间隙锁和临键锁:用于防止幻读问题,确保事务在整个生命周期内看到相同的数据版本。
  • 自动增长锁:用于管理 AUTO_INCREMENT 列的值分配,确保多个事务不会生成重复的自增值。
  • 元数据锁:用于管理对表结构的修改操作,确保在事务执行期间表结构不会发生变化。

6、InnoDB事务与并发控制

InnoDB是MySQL的默认存储引擎,提供了强大的事务支持和并发控制机制。事务确保了数据操作的原子性、一致性、隔离性和持久性(ACID),而并发控制则允许多个事务同时对数据库进行操作,而不影响数据的一致性。

(1)、事务的基本概念
1、事务的ACID特性

事务是数据库中一组逻辑操作的集合。

它具有以下四个重要特性:

  • 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败。如果事务中的任何一个操作失败,整个事务都会被回滚,确保数据库状态的一致性。
  • 一致性(Consistency):事务执行前后,数据库必须保持一致的状态。事务不能破坏数据库的完整性约束(如外键、唯一性等)。
  • 隔离性(Isolation):多个事务并发执行时,每个事务都应独立运行,互不干扰。事务的隔离性由隔离级别决定。
  • 持久性(Durability):一旦事务提交,其对数据库的修改将永久保存,即使系统崩溃也不会丢失。
2、事务的生命周期

一个典型的事务生命周期包括以下几个阶段:

  • 开始事务:使用START TRANSACTION或BEGIN命令显式开始一个事务。
  • 执行操作:在事务中执行一系列SQL操作(如INSERT、UPDATE、DELETE等)。
  • 提交事务:使用COMMIT命令提交事务,将所有修改永久保存到数据库中。
  • 回滚事务:如果事务执行过程中发生错误,可以使用ROLLBACK命令回滚事务,撤销所有修改。
(2)、事务的隔离级别

事务的隔离级别决定了多个事务并发执行时的可见性和行为。InnoDB支持四种标准的隔离级别,分别是:

  • 读未提交(Read Uncommitted)
  • 读已提交(Read Committed)
  • 可重复读(Repeatable Read)
  • 串行化(Serializable)
1、读未提交(Read Uncommitted)
  • 定义:允许事务读取其他事务尚未提交的数据(即"脏读")。这是最低的隔离级别,可能会导致数据不一致问题。
  • 优点:并发性能最高,因为没有锁等待。
  • 缺点:可能会读取到未提交的脏数据,导致数据不一致。
  • 适用场景:适用于对数据一致性要求不高的场景,例如统计分析或报表生成。
2、读已提交(Read Committed)
  • 定义:事务只能读取已经提交的数据,无法读取未提交的数据。这是大多数数据库系统的默认隔离级别。
  • 优点:避免了脏读问题,确保事务只能看到已提交的数据。
  • 缺点:可能会出现不可重复读(Non-repeatable Read)和幻读(Phantom Read)问题。
  • 适用场景:适用于大多数应用场景,尤其是对数据一致性有一定要求但不需要严格隔离的场景。
3、可重复读(Repeatable Read)
  • 定义:事务在整个生命周期内看到的数据版本是一致的,不会受到其他事务的影响。这是InnoDB的默认隔离级别。
  • 优点:避免了脏读、不可重复读和幻读问题,确保事务在整个生命周期内看到相同的数据版本。
  • 缺点:可能会导致更多的锁竞争,尤其是在高并发场景下。
  • 适用场景:适用于对数据一致性要求较高的场景,例如金融交易、库存管理等。
4、串行化(Serializable)
  • 定义:事务以串行的方式执行,完全避免了并发问题。这是最高的隔离级别,确保事务之间不会相互影响。
  • 优点:提供最严格的隔离性,确保数据的一致性。
  • 缺点:并发性能最低,因为事务需要排队执行,可能会导致大量锁等待。
  • 适用场景:适用于对数据一致性要求极高的场景,例如银行转账、证券交易等。
5、隔离级别的选择
  • innodb_default_isolation_level:可以通过该参数设置InnoDB的默认隔离级别。默认值为REPEATABLE READ。
  • 动态设置隔离级别:可以在会话级别或事务级别动态设置隔离级别。
    示例:
java 复制代码
  SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
(3)、并发控制机制

InnoDB使用多种并发控制机制来确保多个事务能够安全地并发执行,主要包括锁机制和多版本并发控制(MVCC)。

1、锁机制

InnoDB的锁机制用于防止多个事务同时对同一数据进行冲突的操作。锁分为行级锁和表级锁,具体包括以下几种类型:

  • 记录锁(Record Locks):锁定单个索引记录,防止其他事务修改或删除该记录。
  • 间隙锁(Gap Locks):锁定索引中的一个区间(即"间隙"),防止其他事务在这个区间内插入新行。
  • 临键锁(Next-Key Locks):结合了记录锁和间隙锁,锁定一个范围内的所有行和间隙,防止其他事务在该范围内插入新行或修改现有行。(InnoDB默认的行级锁)
  • 意向锁(Intention Locks):表明事务打算在表的某些行上加锁,帮助提前检测潜在的锁冲突。
  • 表级锁(Table-Level Locks):锁定整个表,阻止其他事务对该表进行任何操作。
  • 自动增长锁(Auto-Increment Locks):用于管理AUTO_INCREMENT列的值分配,确保多个事务不会生成重复的自增值。
  • 元数据锁(Metadata Locks):用于管理对表结构的修改操作,确保在事务执行期间表结构不会发生变化。
2、多版本并发控制(MVCC)

多版本并发控制(MVCC)是InnoDB实现高并发的核心机制之一。通过MVCC,InnoDB可以允许多个事务同时读取和写入数据,而不会相互阻塞。MVCC的主要思想是为每个事务提供一个独立的快照视图,确保事务在整个生命周期内看到相同的数据版本。

(1)、MVCC的工作原理
  • 隐藏列:InnoDB在每个行中添加了两个隐藏列,用于实现MVCC:

    • DB_TRX_ID:记录最后一次修改该行的事务ID
    • DB_ROLL_PTR:指向回滚段(Undo Log)的回滚指针,用于存储该行的历史版本。
  • 读视图:每个事务都有一个读视图,记录了事务开始时的系统状态。读视图包含了以下信息:

    • 创建读视图时活跃的事务列表。
    • 创建读视图时的最大已提交事务ID。
  • 一致性非锁定读:对于只读查询(如SELECT),InnoDB使用一致性非锁定读(Consistent Non-Locking Read),即读取数据时不加锁。InnoDB会根据读视图判断是否应该返回当前版本的数据,还是从回滚段中查找历史版本的数据。

  • 一致性锁定读:对于修改操作(如SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE),InnoDB会加锁,确保数据的一致性。

(2)、MVCC的隔离级别支持
  • 读未提交(Read Uncommitted):不使用MVCC,允许读取未提交的数据。
  • 读已提交(Read Committed):每个事务都有自己的读视图,但每次读取数据时都会重新生成一个新的读视图。因此,事务可以看到其他事务提交后的最新数据。
  • 可重复读(Repeatable Read):每个事务在其生命周期内只有一个读视图,确保事务在整个生命周期内看到相同的数据版本。
  • 串行化(Serializable):不使用MVCC,所有事务以串行的方式执行,完全避免了并发问题。
(3)、MVCC的优势
  • 提高并发性能:通过MVCC,InnoDB可以允许多个事务同时读取和写入数据,而不会相互阻塞,从而提高并发性能。
  • 减少锁竞争:一致性非锁定读(Consistent Non-Locking Read)允许只读查询不加锁,减少了锁的竞争,提升了系统的吞吐量。
  • 防幻读:在可重复读隔离级别下,InnoDB使用间隙锁(Gap Locks)和临键锁(Next-Key Locks)来防止幻读问题,确保事务在整个生命周期内看到相同的数据版本。
(4)、优化并发性能

为了提高InnoDB的并发性能,建议采取以下优化措施:
1、减少事务的持锁时间

  • 尽量缩短事务的持续时间:事务持有的时间越长,锁的竞争就越激烈。尽量将事务的范围缩小到最小,避免不必要的操作。
  • 批量处理数据:如果需要对大量数据进行修改,可以考虑分批处理,减少单个事务的持有时间。
    2、选择合适的隔离级别
  • 根据应用场景选择隔离级别:不同的隔离级别有不同的锁开销和并发性能。对于大多数应用场景,REPEATABLE READ是一个合理的选择,但在不需要严格一致性的场景下,可以选择READ COMMITTED来减少锁的竞争。
  • 避免使用SERIALIZABLE:除非绝对必要,否则尽量避免使用SERIALIZABLE隔离级别,因为它会导致大量的锁等待,严重影响并发性能。
    3、优化锁粒度
  • 使用行级锁:InnoDB默认使用行级锁,允许多个事务同时对不同行进行操作。相比于表级锁,行级锁的粒度更细,能够有效减少锁冲突,提升并发性能。
  • 避免不必要的锁:在查询中尽量避免使用FOR UPDATE或LOCK IN SHARE MODE,除非确实需要加锁。不必要的锁会增加锁竞争,降低并发性能。
    4、优化索引设计
  • 使用覆盖索引:覆盖索引是指查询所需的所有列都在索引中,而不需要回表查询。通过使用覆盖索引,可以减少I/O操作,提升查询性能。
  • 避免索引过多:过多的索引会增加插入、更新和删除操作的开销,降低并发性能。建议根据实际查询需求,合理设计索引。
    5、优化日志配置
  • 增大重做日志文件大小:通过增大innodb_log_file_size参数,可以减少日志切换的频率,提升写性能。
  • 调整innodb_flush_log_at_trx_commit:根据应用程序对数据安全的要求,选择合适的innodb_flush_log_at_trx_commit值。如果你的应用对性能要求较高,且可以容忍少量数据丢失,可以选择2或0;如果你的应用对数据安全要求较高,建议保持默认值1。
    6、启用并行查询
  • 使用并行查询:从MySQL 8.0开始,InnoDB支持并行查询(Parallel Query),可以在多个线程中并行执行查询操作,提升查询性能。可以通过innodb_parallel_read_threads参数启用并行查询。
(5)、InnoDB并发控制总结

InnoDB的事务管理和并发控制机制是其高性能和高可靠性的核心组成部分。通过事务的 ACID特性、隔离级别、锁机制和多版本并发控制(MVCC),InnoDB能够确保多个事务安全地并发执行,同时保持数据的一致性和完整性。

  • 事务的ACID特性:确保事务的原子性、一致性、隔离性和持久性。
  • 隔离级别:通过不同的隔离级别,平衡并发性能和数据一致性。InnoDB默认使用REPEATABLE READ,但在某些场景下可以选择READ COMMITTED来减少锁竞争。
  • 锁机制:InnoDB使用行级锁、间隙锁、临键锁等多种锁类型,确保多个事务能够安全地并发执行。
  • 多版本并发控制(MVCC):通过MVCC,InnoDB可以允许多个事务同时读取和写入数据,而不会相互阻塞,从而提高并发性能。

乘风破浪会有时,直挂云帆济沧海!!!

相关推荐
m0_748231319 分钟前
深入解析HDFS:定义、架构、原理、应用场景及常用命令
hadoop·hdfs·架构
点点滴滴的记录10 分钟前
mysql和redis的最大连接数
数据库·redis·mysql
yuanbenshidiaos13 分钟前
MYSQL----------数据库优化及锁机制详解
数据库·mysql
长风清留扬20 分钟前
2025年新出炉的MySQL面试题
数据库·sql·mysql·面试
XianxinMao38 分钟前
多模态人工智能在零售业的未来:通过GPT-4 Vision和MongoDB实现智能产品发现
数据库·人工智能·mongodb
侬本多情。39 分钟前
RIP协议在简单网络架构的使用
服务器·网络·网络协议·架构
移动云开发者联盟1 小时前
移动云自研云原生数据库入围国采!
数据库·云原生
遥遥远方 近在咫尺1 小时前
MySQL的事务
数据库·mysql
曹二7471 小时前
创建和管理表
数据库·oracle
大哥喝阔落1 小时前
fitz获取pdf内容
服务器·数据库·pdf