Mysql

索引

01 什么时索引?为什么要使用索引?

  • 官方介绍索引是帮助MySQL高效获取数据数据结构。更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。

  • 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往是存储在磁盘上的文件中的(可能存储在单独的索引文件中,也可能和数据一起存储在数据文件中)。

  • 我们通常所说的索引,包括聚集索引、覆盖索引、组合索引、前缀索引、唯一索引等,没有特别说明,默认都是使用B+树结构组织(多路搜索树,并不一定是二叉的)的索引。

02 常见的索引类型?

  • 主键索引

索引列中的值必须是唯一的,不允许有空值。

  • 普通索引

MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值。

  • 唯一索引

索引列中的值必须是唯一的,但是允许为空值。

  • 全文索引

只能在文本类型CHAR,VARCHAR,TEXT类型字段上创建全文索引。字段长度比较大时,如果创建普通索引,在进行like模糊查询时效率比较低,这时可以创建全文索引。 MyISAM和InnoDB中都可以使用全文索引。

  • 空间索引

MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型。MySQL在空间索引这方面遵循OpenGIS几何数据模型规则。

  • 前缀索引

在文本类型如CHAR,VARCHAR,TEXT类列上创建索引时,可以指定索引列的长度,但是数值类型不能指定。

其他(按照索引列数量分类)

  1. 单列索引

  2. 组合索引

组合索引的使用,需要遵循最左前缀匹配原则(最左匹配原则)。一般情况下在条件允许的情况下使用组合索引替代多个单列索引使用。

最左前缀匹配原则和联合索引的索引存储结构和检索方式是有关系的。

在组合索引树中,最底层的叶子节点按照第一列a列从左到右递增排列,但是b列和c列是无序的,b列只有在a列值相等的情况下小范围内递增有序,而c列只能在a,b两列相等的情况下小范围内递增有序。

03 什么是聚簇索引?什么是非聚簇索引?

  • 聚簇索引(Clustered Index):数据行的物理顺序与索引顺序相同的索引。
  • 非聚簇索引(Non-Clustered Index):数据行的物理顺序与索引顺序不同的索引。

聚簇索引、非聚簇索引和主键索引的主要区别在于它们的存储方式、数据组织结构以及查询效率。‌

  • ‌聚簇索引‌(Clustered Index):
  • 聚簇索引实际上并不是一种单独的索引类型,而是一种数据存储方式。在InnoDB存储引擎中,聚簇索引的叶子节点包含了完整的记录行,这意味着表的数据行都存放在索引树的叶子页中。

  • 由于聚簇索引决定了表中数据的物理存储顺序,因此一张表只能有一个聚簇索引。InnoDB的聚簇索引实际上是将索引和数据保存在同一个B-Tree中,如果没有定义主键,InnoDB会选择一个合适的列作为聚簇索引,如果找不到合适的列,会使用一列隐藏的列DB_ROW_ID作为聚簇索引‌.

  • 非聚簇索引‌( n-clustered Index):

非聚簇索引,也称为二级索引或辅助索引,其叶子节点仅包含主键值,而不包含完整的记录信息。通过非聚簇索引查找记录时,需要先通过非聚簇索引找到主键值,然后再通过主键值到聚簇索引中找到对应的记录行,这个过程称为回表查询。由于非聚簇索引的逻辑顺序与磁盘上行的物理存储顺序不同,因此一个表中可以拥有多个非聚簇索引。非聚簇索引的叶子节点中保存的不是指向行的物理指针,而是行的主键值,这有助于减少移动数据或分裂时维护非聚簇索引的开销‌

  • 主键索引

主键索引是一种特殊的聚簇索引,它确保了数据的唯一性。在关系数据库中,主键是唯一标识表中每一行的列。当为表设置主键时,MySQL会自动为该主键创建一个聚簇索引。因此,主键索引既是聚簇索引的一种,也是数据库表中数据组织的核心‌。

我们常说的聚簇索引不一定是主键索引,而主键索引一定是聚簇索引。

可以这样理解,就是在聚簇索引上建立的索引,都是非聚簇索引(也称为二级索引或辅助索引)。因为一个表中只能有一个聚簇索引,其他都是非聚簇索引。

引擎上的区别

InnoDB

使用的是聚簇索引,比如若使用“where id=5“的条件查找主键,则按照B+树的检索算法,即可查找到对应的叶子节点,之后获得行数据。

若对employee_name 列进行条件搜索,则需要两个步骤:

  1. 在辅助索引B+树中检索非主键列(eg:employee_name ),到达其叶子节点获取对应的主键。
  2. 用主键在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点获取整行数据。(重点在于通过其他键需要建立辅助索引)

聚簇索引默认是主键,若表中无定义主键,InnoDB会选择一唯一且非空的索引代替。若无这样的索引,InnoDB会隐式定义一个主键(类似 oracle中的Rowld,使用一列隐藏的列DB_ROW_ID作为聚簇索引‌)作为聚簇索引。

若已设了主键为聚簇索引又希望再单独设置聚簇索引,必须先删除原主键,然后添加,最后恢复设置主键即可。

MyISAM

便用的是非聚簇索引,只是存储的内容不同,主键索引B+树的节点存储了主键, 辅助键索引IB+树存储了辅助键。

表数据存储在独立的地方(MYN文件),这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。
由于索引树是独立的,通过辅助键检索无需访问主键的索引树。

实例演示:

  1. 聚簇索引的使用
1
2
3
4
5
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
employee_name VARCHAR(100),
department_name VARCHAR(100)
);

在这个例子中,employee_id是聚簇索引,因为它直接存储了数据行。

  1. 非聚簇索引的使用
1
CREATE INDEX idx_department ON employees (department_name);

idx_department是非聚簇索引,因为它指向数据行的位置。

常见问题解答
  • Q: 为什么聚簇索引通常比非聚簇索引更快?
    A: 聚簇索引的数据存储与索引结构结合在一起,减少了额外的数据查找步骤。
  • Q: 我应该在什么情况下使用非聚簇索引?
    A: 当你需要基于非主键列进行频繁查询时,非聚簇索引是一个好的选择。

04 什么是回表?

MySQL中的索引有很多中不同的分类方式,可以按照数据结构分,可以按照逻辑角度分,也可以按照物理存储分,其中,按照物理存储方式,可以分为聚簇索引和非聚簇索引。

我们日常所说的主键索引,其实就是聚簇索引(ClusteredIndex); 主键索引之外,其他的都称之为非主键索引,非主键索引也被称为二级索引(SecondaryIndex),或者叫作辅助索引。

对于主键索引和非主键索引,使用的数据结构都是 B+Tree,唯一的区别在于叶子结点中存储的内容不同:

  • 主键索引的叶子结点存储的是一行完整的数据。
  • 非主键索引的叶子结点存储的则是主键值。

这就是两者最大的区别。

所以,当我们需要查询的时候:

  1. 如果是通过主键索引来查询数据,例如 select * from user where id=100,那么此时只需要搜索主键索引的 B+Tree 就可以找到数据。

  2. 如果是通过非主键索引来查询数据,例如select * from user where username='javaboy',那么此时需要先搜索 username 这一列索引的 B+Tfee,搜索完成后得到主键的值,然后再去搜索主键索引的 B+Tree,就可以获取到一行完整的数据。

对于第二种查询方式而言,一共搜索了两棵 B+Tree,第一次搜索 B+Tree 拿到主键值后再去搜索主键索引的B+Tree,这个过程就是所谓的回表。

从上面的分析中我们也能看出,通过非主键索引查询要扫描两棵 B+Tree,而通过主键索引查询只需要扫描一棵B+Tree,所以如果条件允许,还是建议在查询中优先选择通过主键索引进行搜索。

05 覆盖索引?

覆盖索引是一种避免回表查询的优化策略:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表速度更快。

具体的实现方式:

将被查询的字段建立普通索引或者联合索引,这样的话就可以直接返回索引中的的数据,不需要再通过聚集索引去定位行记录,避免了回表的情况发生。

1
EXPLAIN SELECT user name,user age,user level FROM usersWHERE user name =tom'AND user_age =17;

image-20250322182804330

  • 覆盖索引的定义与注意事项:
    如果一个索引包含了 所有需要査询的字段的值 (不需要回表),这个索引就是覆盖索引
  • MySQL只能使用B+Tree索引做覆盖索引(因为只有B+树能存储索引列值)
  • 在explain的Extra列,如果出现Using index 表示 使用到了覆盖索引,所取的数据完全在索引中就能拿到

06 索引的创建原则

在进行索引设计的时候,应该保证索引字段占用的空间越小越好,这只是一个大的方向,还有一些细节点需要注意下:

  1. 适合索引的列是出现在where字句中的列,或者连接子句中指定的列

  2. 基数较小的表,索引效果差,没必要创建索引

基数比较小,比如dv-distinct value,状态列,只有1234四种状态,此时用索引不会增加效率

  1. 在选择索引列的时候,越短越好,可以指定某些列的一部分,没必要用全部字段的值

假设有一个表 users,其中有一个字段 email,类型是 VARCHAR(255)

直接索引整个字段

1
CREATE INDEX idx_email ON users(email);

这个索引会很大,因为每个邮箱地址都很长。

只索引前 10 个字符

1
CREATE INDEX idx_email_prefix ON users(email(10));

这个索引只存储邮箱的前 10 个字符,索引会小很多,查询速度也会更快。

  1. 不要给表中的每一个字段都创建索引,并不是索引越多越好

为什么不要给每个字段都创建索引?

  1. 写操作变慢
    每次插入、更新或删除数据时,数据库需要更新所有索引,写操作会变慢。
  2. 浪费空间
    每个索引都会占用磁盘空间,索引太多会浪费存储资源。
  3. 查询不一定更快
    索引太多会让数据库的查询优化器难以选择,反而可能降低查询效率。
  4. 有些字段不需要索引
    比如性别(只有男/女)或者很少用于查询的字段,创建索引没有意义。
  1. 定义有外键的数据列一定要创建索引
  1. 提高关联查询性能
    外键通常用于表之间的关联查询(如 JOIN)。如果没有索引,数据库需要全表扫描来查找匹配的记录,效率很低。
  2. 保证数据一致性
    外键约束会检查父表和子表的数据一致性。如果没有索引,每次插入或更新子表时,数据库需要扫描父表,影响性能。
  3. 避免死锁
    在并发操作中,外键列没有索引可能导致锁冲突和死锁问题。索引可以减少锁的竞争,提高并发性能。

例子:假设有两个表:

  • orders
    
    1
    2
    3
    4
    5
    6
    7
    8

    (订单表)

    - `order_id` (主键)
    - `customer_id` (外键,关联 `customers` 表)

    - ```
    customers
    (客户表) - `customer_id` (主键)

如果 orders 表的 customer_id 没有索引:

1
SELECT * FROM orders JOIN customers ON orders.customer_id = customers.customer_id;

数据库需要扫描整个 orders 表来查找匹配的 customer_id,性能会很差。

如果为 customer_id 创建索引:

1
CREATE INDEX idx_customer_id ON orders(customer_id);

数据库可以快速定位匹配的记录,查询性能显著提升。

6.更新频繁的字段不要有索引

如果字段更新频繁,索引的维护成本会很高,导致写操作变慢。频繁更新索引会增加锁的竞争,可能导致并发性能下降,甚至引发死锁。

7.创建索引的列不要过多,可以创建组合索引,但是组合索引的列的个数不建议太多

每个索引都会占用磁盘空间。如果索引列过多,索引会变得非常大,浪费存储资源。组合索引的列数越多,索引的选择性越低(即索引值重复的可能性越大),查询效率会下降。

8.大文本、大对象不要创建索引

大文本和大对象字段通常用于存储描述性内容,很少用于查询条件。为它们创建索引往往没有实际意义。通常包含大量数据。如果为它们创建索引,索引会变得非常大,浪费存储资源。

07 什么情况下不适合添加索引?

  1. 表数据太少的数据表不适合建立索引
  2. 经常增、删、改的表不适合建立索引,因为索引需要动态维护,增加索引会加大数据表的维护难度。
  3. 对于区分度不高(字段值的唯一性不高)的字段,不适合建立索引。例如性别字段,由于这些列的取值很少,正常只有男、女。在查询的结果中,结果集的数据行占了表中数据行的最大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
  4. 定义为 textbit 数据类型的列不适合建立索引,因为这些列的数据量要么相当大,要么取值很少。
  5. WHERE 条件中用不到的字段不适合创建索引
  6. 频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录,还会更新索引,加重 IO 负担。
  7. 在查询中很少使用的列不适合创建索引。因为这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了数据表的维护速度和增大了空间消耗。

哪些情况适合创建索引?

  1. 频繁作为查询条件的字段(用在 WHERE 子句中的列)适合建立索引,可以加快查询的速度。
  2. 表与表连接用于多表联合查询的约束条件的字段适合建立索引
  3. 查询中排序的字段适合建立索引。因为索引已经排序,这样查询时可以利用索引的排序,加快排序速度。
  4. 查询中用于统计或者分组的字段适合建立索引
  5. 经常需要根据范围进行搜索的列上适合建立索引,因为索引已经排序,其指定的范围是连续的。

08 什么情况下索引会失效?

1. 对索引字段进行了运算或者使用了函数

原因:
如果对索引字段进行了运算(如 WHERE age + 1 > 30)或使用了函数(如 WHERE UPPER(name) = 'JOHN'),数据库无法直接使用索引,因为索引存储的是原始值,而不是运算或函数处理后的值。

例子:

1
2
-- 索引失效
SELECT * FROM users WHERE YEAR(create_time) = 2023;

2. 查询中的数据类型和字段类型不一致

原因:
如果查询条件中的数据类型与索引字段的类型不一致(如字段是 VARCHAR,但查询条件是数字),数据库可能无法使用索引,因为它需要进行隐式类型转换。

例子:

1
2
-- 索引失效
SELECT * FROM users WHERE phone = 123456789; -- phone 是 VARCHAR 类型

3. 违反了索引的最左匹配原则

原因:
对于组合索引,查询条件必须从索引的最左列开始,否则索引无法发挥作用。如果跳过了最左列,索引会失效。

例子:

1
2
-- 组合索引 (name, age)
SELECT * FROM users WHERE age = 30; -- 索引失效,因为跳过了 name

4. 全表扫描更快

原因:
在某些情况下,数据库优化器会认为全表扫描比使用索引更快(比如表中数据量很小,或者索引的选择性很低),因此会选择不使用索引。

例子:

1
2
-- 如果表只有 10 行数据,数据库可能选择全表扫描
SELECT * FROM users WHERE gender = 'male'; -- gender 字段选择性低

09 SQL中的like,%在查询字符串前面/后面是否会走索引?为什么?

  1. % 在查询字符串前面(如 %John
    不会走索引,因为索引是按照字段值的前缀组织的(如 B+Tree 索引),% 在前面时无法利用索引的前缀匹配特性,数据库只能进行全表扫描。
  2. % 在查询字符串后面(如 John%
    会走索引,因为 % 在后面时可以利用索引的前缀匹配特性,数据库可以快速定位到符合条件的记录。
  3. % 在查询字符串中间(如 %John%
    不会走索引,因为 % 在中间时无法利用索引的前缀匹配特性,数据库只能进行全表扫描。
  4. % 的精确匹配(如 John
    会走索引,因为精确匹配可以直接利用索引进行查找,效率最高。

LIKE 查询是否会走索引,取决于 % 的位置。如果 % 在查询字符串前面(如 %John)或中间(如 %John%),索引不会被使用,数据库会进行全表扫描;如果 % 在查询字符串后面(如 John%),索引会被使用,因为可以利用索引的前缀匹配特性。”

事务

01 MySQL事务特性(ACID)

MySQL 的事务是数rrr据库操作的基本单位,它确保一组操作要么全部成功,要么全部失败,从而保证数据的一致性和完整性。

1. 原子性(Atomicity)

  • 定义:事务是一个不可分割的操作单元,要么全部执行成功,要么全部失败回滚。
  • 作用:确保事务中的操作要么全部生效,要么全部不生效,不会出现部分成功、部分失败的情况。
  • 实现方式:通过 Undo Log(回滚日志)实现,如果事务失败,MySQL 会利用 Undo Log 回滚所有操作。

例子
转账操作中,A 向 B 转账 100 元,必须同时完成 A 账户扣款和 B 账户加款。如果其中一步失败,整个操作会回滚。

2. 一致性(Consistency)

  • 定义:事务执行前后,数据库必须保持一致性状态,即数据必须符合所有定义的约束(如唯一性、外键等)。
  • 作用:确保数据库从一个有效状态转换到另一个有效状态。
  • 实现方式:通过 约束(如主键、外键、唯一性等)和 原子性 共同保证。

例子
转账操作中,A 和 B 的账户总额在事务执行前后必须保持一致。


3. 隔离性(Isolation)

  • 定义:多个并发事务之间是相互隔离的,一个事务的操作不会被其他事务干扰。
  • 作用:防止并发事务导致的数据不一致问题。
  • 实现方式:通过 锁机制MVCC(多版本并发控制) 实现。
  • 隔离级别
    • 读未提交(Read Uncommitted):最低级别,事务可以读取未提交的数据。
    • 读已提交(Read Committed):事务只能读取已提交的数据。
    • 可重复读(Repeatable Read):MySQL 默认级别,确保同一事务中多次读取的数据一致。
    • 串行化(Serializable):最高级别,事务完全串行执行。

例子
事务 A 和事务 B 同时操作同一行数据,隔离性确保它们不会互相干扰。


4. 持久性(Durability)

  • 定义:事务一旦提交,对数据的修改就是永久性的,即使系统崩溃也不会丢失。
  • 作用:确保数据的可靠性。
  • 实现方式:通过 Redo Log(重做日志) 实现,事务提交时,修改会先写入 Redo Log,再写入磁盘。

例子
转账操作提交后,即使数据库崩溃,数据也不会丢失。

02 事务的并发问题?

事务并发引起的三大问题

  • 脏读:一个事务会读取到另一个事务未提交的数据。(读取到未提交数据)

    例子:事务A修改了数据但还未提交,事务B读取到了事务A修改的数据。然后事务A因为某些错误回滚了,这个时候事务B读取到的数据就是脏的,这就是脏读。

  • 不可重复读:在同一事务内,事务两次读取到的数据是不一样的。(原数据中同一条数据被修改或被删除)

    例子:事务A读取了一条数据之后,事务B修改了这条数据并提交了事务,然后事务A再次读取这条数据,就会发现两次结果不一致。这就是不可重复读。

  • 幻读:事务中的同一个查询在不同的时间产生不同的行集,这个就是幻读问题。(数据总条数新增)

    例子:事务A使用一定的条件查询,然后事务B增加了符合条件的记录,当事务A再次查询的时候,发现两次查询的结果集不一样,好像产生了幻觉。这就是幻读。

不可重复读和幻读,都是读取到其他事务已经提交的数据。而脏读是读取到其他事务还未提交的数据

03 MySQL可重复读级别下如何解决幻读问题

MySQLInnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:

  • 针对快照读(普通 select 语句),是通过 MVCC方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。

  • 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

这两个解决方案是很大程度上解决了幻读现象,但是还是有个别的情况造成的幻读现象是无法解决的。

——————————————幻读—————————————

同一个查询在不同的时间产生不同的结果集时,事务中就会出现所谓的幻象问题。例如,如果 SELECT 执行了两次,但第二次返回了第一次没有返回的行,则该行是“幻像”行。

举个例子,假设一个事务在 T1 时刻和 T2 时刻分别执行了下面查询语句,途中没有执行其他任何语句:

1
SELECT * FROM t_test WHERE id > 100;

只要 T1 和 T2 时刻执行产生的结果集是不相同的,那就发生了幻读的问题,比如:

  • T1 时间执行的结果是有 5 条行记录,而 T2 时间执行的结果是有 6 条行记录,那就发生了幻读的问题。
  • T1 时间执行的结果是有 5 条行记录,而 T2 时间执行的结果是有 4 条行记录,也是发生了幻读的问题。

—————————————————–//////————————————————

快照读是如何避免幻读

可重复读隔离级是由 MVCC(多版本并发控制)实现的,实现的方式是开始事务后(执行 begin 语句后),在执行第一个查询语句后,会创建一个 Read View,后续的查询语句利用这个 Read View,通过这个 Read View 就可以在 undo log 版本链找到事务开始时的数据,所以事务过程中每次查询的数据都是一样的,即使中途有其他事务插入了新纪录,是查询不出来这条数据的,所以就很好了避免幻读问题。

当前读是如何避免幻读

当前读通过 锁机制 来避免幻读,具体实现如下:

  1. Next-Key Lock(临键锁)
    MySQL 的 InnoDB 引擎使用 ​Next-Key Lock,它是 ​记录锁(Record Lock)​ 和 ​间隙锁(Gap Lock)​ 的组合。
    • 记录锁:锁定当前记录。
    • 间隙锁:锁定记录之间的间隙,防止其他事务插入新记录。
  2. 锁定范围
    当执行当前读(如 SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE)时,MySQL 会对查询范围内的记录和间隙加锁,防止其他事务插入新记录。

04 MVCC解决了什么问题?

1.什么是MVCC?

MVCC全称是【Multi-Version ConCurrency Control】,即多版本控制协议。

多版本控制(Multiversion Concurrency Control): 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。在内部实现中,InnoDB通过undo log保存每条数据的多个版本,并且能够找回数据历史版本提供给用户读,每个事务读到的数据版本可能是不一样的。在同一个事务中,用户只能看到该事务创建快照之前已经提交的修改和该事务本身做的修改。

MVCC只在 Read Committed 和 Repeatable Read两个隔离级别下工作。其他两个隔离级别和MVCC不兼容,Read Uncommitted总是读取最新的记录行,不需要MVCC的支持;Serializable 则会对所有读取的记录行都加锁,单靠MVCC无法完成。

MySQL的InnoDB存储引擎默认事务隔离级别是RR(可重复读),是通过 “行级锁+MVCC”一起实现的,正常读的时候不加锁,写的时候加锁。而 MCVV 的实现依赖:隐藏字段、Read View、Undo log。

2.MVCC解决了什么问题

在并发事务下,可能会产生如下问题:

1.脏读 :当前事务读取到其它事务未提交的数据。
2.脏写 : 事务B提交后,将事务A提交的数据覆盖。
3.不可重复读:在同一个事务中,不同时间段执行相同的查询语句,得到的结果集不相同。
4.幻读:事务A读取到了事务B新增的数据。

MVCC可重复读模式下,解决了事务的脏读、脏写、不可重复读等问题,但是还是存在幻读问题,幻读问题可以使用间隙锁进行解决。

05 MVCC什么原理?

MVCC全称是【Multi-Version ConCurrency Control】,即多版本控制协议。是事务隔离级别的无锁实现方式,用于提高事务的并发性能

Mysql中的innoDB中就是使用这种方法来提高读写事务的并发性能、原因是MVCC是一种不采用锁来控制事务的方式,是一种非堵塞、同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题。

总之:就是MVCC是通过保存数据的历史版本,根据比较版本号来处理数据是否显示,从而达到读取数据的时候不需要加锁就可以保证事务隔离性的效果。

MVCC的实现原理是依靠:记录中的3个隐含字段、undo log日志、Read View实现的。

1、对于 InnoDB ,聚簇索引记录中包含 3 个隐藏的列:

ROW ID:隐藏的自增 ID,如果表没有主键,InnoDB 会自动按 ROW ID 产生一个聚集索引树。
事务 ID:记录最后一次修改该记录的事务 ID。
回滚指针:指向这条记录的上一个版本。
2、undo log日志:
insert undo log:事务进行插入操作时产生、在事务回滚时需要,提交事务后可以被立即丢

update undo log:进行update、delete时产生的undo log、不仅在回滚事务时需要、在快照读时也需要。所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除(purge类似jvm中的gc垃圾回收器)

3、Read View(读视图)
read view读视图就是在进行快照读时会产生一个read view视图、在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)。

判断是否可读

小于最小活动事务ID,可读。
大于最大活动事务ID,可读。
之间,在集合中,不可读,不在集合中,可读。
ReadView生成时机

READ COMMITTED —— 每次读取数据前都生成一个ReadView。
REPEATABLE READ —— 在第一次读取数据时生成一个ReadView。
4:总结出 MVCC 实现的原理大致是:
InnoDB 每一行数据都有一个指向上一个版本数据在undo log日志里的位置指针。如果要执行更新操作,会将原记录放入 undo log 中,并通过隐藏的回滚指针指向 undo log 中的原记录。其它事务此时需要查询时,就是查询 undo log 中这行数据的最后一个历史版本。

MVCC 最大的好处是读不加锁,读写不冲突,极大地增加了 MySQL 的并发性。通过 MVCC,保证了事务的隔离性。

mysql的锁机制