MySQL 进阶学习笔记(包括MySQL的存储引擎、索引、SQL优化、视图、存储过程、触发器、锁InnoDB引擎和MySQL管理)的相关内容详细版

目录

  • [MySQL 进阶学习笔记](#MySQL 进阶学习笔记)
    • [1. 存储引擎(Storage Engines)](#1. 存储引擎(Storage Engines))
      • [1.1 MySQL 体系结构](#1.1 MySQL 体系结构)
      • [1.2 存储引擎简介](#1.2 存储引擎简介)
      • [1.3 存储引擎特点](#1.3 存储引擎特点)
      • [1.4 存储引擎选择](#1.4 存储引擎选择)
    • [2. 索引(Indexes)](#2. 索引(Indexes))
      • [2.1 索引概述](#2.1 索引概述)
      • [2.2 索引结构](#2.2 索引结构)
      • [2.3 索引分类](#2.3 索引分类)
      • [2.4 索引语法](#2.4 索引语法)
      • [2.5 SQL 性能分析](#2.5 SQL 性能分析)
        • [2.5.1 SQL执行频率](#2.5.1 SQL执行频率)
        • [2.5.2 慢查询日志](#2.5.2 慢查询日志)
        • [2.5.3 `SHOW PROFILES`](#2.5.3 SHOW PROFILES)
        • [2.5.4 `EXPLAIN`执行计划](#2.5.4 EXPLAIN执行计划)
      • [2.6 索引使用规则](#2.6 索引使用规则)
        • [2.6.1 验证索引效率](#2.6.1 验证索引效率)
        • [2.6.2 最左前缀法则](#2.6.2 最左前缀法则)
        • [2.6.3 索引失效情况](#2.6.3 索引失效情况)
        • [2.6.4 SQL 提示](#2.6.4 SQL 提示)
        • [2.6.5 覆盖索引 & 回表查询](#2.6.5 覆盖索引 & 回表查询)
        • [2.6.6 前缀索引](#2.6.6 前缀索引)
        • [2.6.7 单列索引 vs 联合索引](#2.6.7 单列索引 vs 联合索引)
      • [2.7 索引设计原则](#2.7 索引设计原则)
    • [3. SQL 优化](#3. SQL 优化)
      • [3.1 插入数据优化](#3.1 插入数据优化)
      • [3.2 主键优化](#3.2 主键优化)
      • [3.3 ORDER BY 优化](#3.3 ORDER BY 优化)
      • [3.4 GROUP BY 优化](#3.4 GROUP BY 优化)
      • [3.5 LIMIT 优化](#3.5 LIMIT 优化)
      • [3.6 COUNT 优化](#3.6 COUNT 优化)
      • [3.7 UPDATE 优化](#3.7 UPDATE 优化)
    • [4. 视图、存储过程、触发器](#4. 视图、存储过程、触发器)
      • [4.1 视图(VIEW)](#4.1 视图(VIEW))
        • [4.1.1 作用](#4.1.1 作用)
        • [4.1.2 语法](#4.1.2 语法)
        • [4.1.3 示例](#4.1.3 示例)
        • 4.1.4视图的检查选项
        • [4.1.5 视图的更新](#4.1.5 视图的更新)
      • [4.2 存储过程(Stored Procedure)](#4.2 存储过程(Stored Procedure))
        • [4.2.1 介绍:](#4.2.1 介绍:)
        • [4.2.2 语法](#4.2.2 语法)
        • [4.2.3 示例](#4.2.3 示例)
        • [4.2.4 变量](#4.2.4 变量)
        • [4.2.5 `if`](#4.2.5 if)
        • [4.2.6 `case`](#4.2.6 case)
        • [4.2.7 `while`](#4.2.7 while)
        • [4.2.8 `repeat`](#4.2.8 repeat)
        • [4.2.9 `loop`](#4.2.9 loop)
        • [4.2.10 游标](#4.2.10 游标)
        • [4.2.11 条件处理程序](#4.2.11 条件处理程序)
      • [4.3 存储函数](#4.3 存储函数)
      • [4.4 触发器(Trigger)](#4.4 触发器(Trigger))
    • [5. 锁(Locks)](#5. 锁(Locks))
      • [5.1 全局锁(Global Lock)](#5.1 全局锁(Global Lock))
        • [5.1.1 全局锁介绍](#5.1.1 全局锁介绍)
        • [5.1.2 加全局锁](#5.1.2 加全局锁)
        • [5.1.3 释放全局锁](#5.1.3 释放全局锁)
        • [5.1.4 一致性数据备份](#5.1.4 一致性数据备份)
          • [(1)MyISAM 存储引擎:](#(1)MyISAM 存储引擎:)
          • [(2)InnoDB 存储引擎:](#(2)InnoDB 存储引擎:)
      • [5.2 表级锁(Table-Level Lock)](#5.2 表级锁(Table-Level Lock))
        • [5.2.1 表锁(Table Lock)](#5.2.1 表锁(Table Lock))
        • [5.2.2 元数据锁(MDL, Metadata Lock)](#5.2.2 元数据锁(MDL, Metadata Lock))
        • [5.2.3 意向锁(Intention Lock)](#5.2.3 意向锁(Intention Lock))
      • [5.3 行级锁(Row-Level Lock)](#5.3 行级锁(Row-Level Lock))
        • [5.3.1 行锁(Record Lock)](#5.3.1 行锁(Record Lock))
        • [5.3.2 间隙锁(Gap Lock)和临键锁(Next-Key Lock)](#5.3.2 间隙锁(Gap Lock)和临键锁(Next-Key Lock))
    • [6. InnoDB 引擎](#6. InnoDB 引擎)
      • [6.1 InnoDB 的逻辑存储结构](#6.1 InnoDB 的逻辑存储结构)
      • [6.2 InnoDB 的架构](#6.2 InnoDB 的架构)
        • [6.2.1 内存结构](#6.2.1 内存结构)
        • [6.2.2 磁盘结构](#6.2.2 磁盘结构)
        • [6.2.3 后台线程](#6.2.3 后台线程)
      • [6.3 事务原理](#6.3 事务原理)
        • [6.3.1 事务概述](#6.3.1 事务概述)
        • [6.3.2 Redo Log(保证事务持久性)](#6.3.2 Redo Log(保证事务持久性))
        • [6.3.3 Undo Log(保证事务原子性)](#6.3.3 Undo Log(保证事务原子性))
      • [6.4 MVCC(多版本并发控制)](#6.4 MVCC(多版本并发控制))
        • [6.4.1 基本概念](#6.4.1 基本概念)
        • [6.4.2 隐藏字段与 Undo Log 版本链](#6.4.2 隐藏字段与 Undo Log 版本链)
        • [6.4.3 读视图(ReadView )的介绍](#6.4.3 读视图(ReadView )的介绍)
        • [6.4.4 原理分析(以 RC 隔离级别为例)](#6.4.4 原理分析(以 RC 隔离级别为例))
    • [7. MySQL 管理](#7. MySQL 管理)

MySQL 进阶学习笔记

1. 存储引擎(Storage Engines)

1.1 MySQL 体系结构

MySQL 主要由以下几部分组成:

  • 连接层:管理客户端连接,如用户认证、权限管理等。
  • 服务层:包括 SQL 解析、优化、执行等功能。
  • 存储引擎层:处理数据存储和索引管理,不同存储引擎具有不同特性。
  • 文件系统层:负责与操作系统交互,进行数据存储。

MySQL 采用 插件式存储引擎架构,不同存储引擎负责数据的存取管理。

1.2 存储引擎简介

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可以被称为表类型。MySQL 支持多种存储引擎,每种引擎适用于不同的应用场景.

1. 查询建表语句

sql 复制代码
-- 查询建表语句,默认存储引擎:InnoDB
show create table 表名;

-- 结果
CREATE TABLE `users` (
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(50) NOT NULL,
    `age` INT DEFAULT 18,
    `email` VARCHAR(100) UNIQUE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'

2. 在建表时,指定存储引擎

sql 复制代码
CREATE TABLE 表名 (
    字段1 字段类型1 [COMMENT 字段1注释]
    ......
    字段n 字段类型n [COMMENT 字段n注释]
) ENGINE=InnoDB [COMMENT 表注释];

3. 查看当前数据库支持的存储引擎

sql 复制代码
SHOW ENGINES;
  • InnoDB:支持事务,行级锁,高并发。
  • MyISAM:查询速度快,但不支持事务。
  • MEMORY:数据存储在内存中,适用于临时计算。
  • CSV:数据以 CSV 格式存储,方便与其他系统交互。
  • ARCHIVE:适用于数据归档,仅支持插入和查询。

InnoDB体系结构:

1.3 存储引擎特点

存储引擎 事务支持 锁机制 适用场景 备注
InnoDB 支持 行级锁 高并发、事务处理 MySQL 默认存储引擎,是一种兼顾高可靠性和高性能通用的存储引擎,DML操作遵循ACID模型,支持外键FOREIGN KEY约束,保证数据的完整性和正确性。 xxx.ibd:xxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引。
MyISAM 不支持 表级锁 读多写少的场景 查询速度快,但不适合高并发,是MySQL早期默认存储引擎,不支持事务,不支持外键 xxx.sdi:存储表结构信息 xxx.MYD:存储数据 xxx.MYI:存储索引
MEMORY 不支持 表级锁 临时数据存储 断电数据丢失,只能将这些表作为临时表或缓存使用,内存存放,速度快,hash索引(默认) xxx.sdi:存储表结构信息
CSV 不支持 无索引 数据交换 适用于简单存储,查询性能低
ARCHIVE 不支持 无索引 日志存储 仅支持 INSERT 和 SELECT

xxx.ibd:xxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引。

参数:innodb_file_per_table

1.4 存储引擎选择

选择存储引擎时需要考虑以下因素:

  1. 事务支持 :如果需要事务(ACID)支持,外键支持,应选择 InnoDB
  2. 查询性能 :读和插入多,更新和删除少,对事务并发性和完整性要求不高的场景可以使用 MyISAM
  3. 数据持久性 :如果数据必须长期存储,避免使用 MEMORY。MEMORY访问速度快,但是对表大小有限制,太大的表无法缓存在内存中,且无法保证数据安全性。
  4. 数据更新频率 :高并发写入应选择 InnoDB ,归档数据可用 ARCHIVE
  5. 索引需求 :需要全文索引的应用可以使用 MyISAMInnoDB(8.0+)。

2. 索引(Indexes)

2.1 索引概述

索引是一种数据结构(有序) ,用于加速数据库的查询操作。通过索引,MySQL 可以更高效地查找数据,减少磁盘 I/O,提高查询速度。

索引的作用:

  • 提高查询性能 :加快 SELECT 语句的执行。
  • 提高排序性能:支持 ORDER BY 优化。
  • 提高分组性能:GROUP BY 操作更高效。
  • 约束数据完整性:如 PRIMARY KEY、UNIQUE 约束。

索引优势:

  • 提高数据检索的效率,降低数据库的IO成本。
  • 通过索引对数据进行排序,降低数据排序成本,降低CPU的消耗。

索引劣势:

  • 占用空间。
  • 索引提高了查询效率,同时也降低了更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低。

2.2 索引结构

MySQL 主要使用 B+ 树索引哈希索引

  • B+ 树索引(默认):适用于范围查询、排序。
  • 哈希索引(MEMORY 引擎):仅适用于等值精确查询,不支持范围查询。
  • R-tree(空间索引):是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少。
  • Full-text(全文索引):是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucene,Solr,ES。

为什么InnoDB存储引擎选择使用B+tree索引结构?

  • 相对于二叉树,层级更少,搜索效率更高
  • 对于B-tree,无论是叶子结点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
  • 相对Hash索引,B+tree支持范围匹配及排序操作;

2.3 索引分类

索引类型 说明 适用场景
常规索引(INDEX) 提高查询速度 频繁查询的字段
唯一索引(UNIQUE) 保证字段唯一 唯一性约束,如邮箱、用户名
主键索引(PRIMARY KEY) 约束表唯一键 ID 等主键字段
复合索引(Composite Index) 多列索引,提高查询效率 WHERE 组合查询
全文索引(FULLTEXT) 适用于文本搜索 文章搜索

在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:

分类 含义 特点
聚集索引(Clustered Index) 将数据存储与索引放到了一块,索引结果的叶子结点保存了行数据 必须有,而且只有一个
二级索引(Secondary Index) 将数据与索引分开存储,索引解构的叶子结点关联的是对应的主键 可以存在多个

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引。
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
  • 如果没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。

2.4 索引语法

  • 创建索引

    sql 复制代码
    CREATE [UNIQUE|FULLTEXT] INDEX idx_name ON table_name(INDEX_column_name [asc|desc],...); -- [asc|desc]:指定该字段是升序创建索引还是降序创建索引,默认升序
  • 删除索引

    sql 复制代码
    DROP INDEX idx_name ON table_name;
  • 查看索引

    sql 复制代码
    SHOW INDEX FROM table_name;

2.5 SQL 性能分析

2.5.1 SQL执行频率

​ MySQL客户端连接成功后,通过show [session|global] status 命令可以提供服务器状态信息,查看SQL语句的执行命令。通过如下指令,可以查看当前数据库的INSERT、IPDATE、DELETE、SELECT的访问频次:

sql 复制代码
SHOW GLOBAL STATUS LIKE 'Com_______' -- 七个下划线代表七个占位符

-- 例如
SHOW STATUS LIKE 'Com_select'; -- 查询 SELECT 语句执行次数
SHOW STATUS LIKE 'Com_insert'; -- 查询 INSERT 语句执行次数
2.5.2 慢查询日志

慢日志查询记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。

MySQL的慢日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:

  • 配置文件中配置:

    sql 复制代码
    -- 开启慢日志查询开关
    slow_query_log = 1
    
     -- 设置慢日志时间为2秒,SQL语句执行时间超过 2 秒,就会被视为慢查询,记录慢查询日志
    long_query_time = 2

​ 配置完毕之后,通过以下指令重新启动服务器进行测试,查看慢日志文件中记录的信息/var/lib/mysqld/localhost-slow.log。

  • 重启MySQL

    sql 复制代码
    systemctl restart mysqld

或者使用如下方法启用慢查询日志

  • 启用慢查询日志:

    sql 复制代码
    SET GLOBAL slow_query_log = 1; -- 开启慢日志查询开关
    
    SET GLOBAL long_query_time = 2; -- 设置慢日志时间为2秒,SQL语句执行时间超过 2 秒,就会被视为慢查询,记录慢查询日志
  • 查看慢查询日志文件位置:

    sql 复制代码
    SHOW VARIABLES LIKE 'slow_query_log_file';
2.5.3 SHOW PROFILES

show profiles用于查看 SQL 语句执行的性能概况,能够在做SQL优化时,帮助我们了解时间都耗费到哪里去了。通过have_profiling参数,能够看到当前MySQL是否支持profile操作。

  • 插卡当前MySQL是否支持profile操作

    sql 复制代码
    SELECT @@have_profiling;

默认profiling是关闭的,可以通过set语句在session/global级别开启profiling。

  • 启用 SHOW PROFILES 功能:

    sql 复制代码
    SET profiling = 1;
  • 执行 SQL 语句后查看性能(执行耗时):

    sql 复制代码
    -- 查看每条SQL的基本耗时情况
    SHOW PROFILES;
    
    -- 查看指定query_id的SQL语句各个阶段的耗时情况
    SHOW PROFILE FOR QUERY query_id;
    -- 例如
    SHOW PROFILE FOR QUERY 1; -- 查看某条查询的详细性能
    
    -- 查看指定query_id的SQL语句CPU的使用情况
    SHOW PROFILE CPU FOR QUERY query_id;
2.5.4 EXPLAIN执行计划

EXPLAIN 用于分析 SQL 语句的执行计划,优化查询。EXPLAIN或者DESC命令获取MySQL如何执行SELECT语句的信息,包括在SELECT语句执行过程中表如何连接和连接的顺序。

  • 语法:

    sql 复制代码
    -- 直接在select语句之前加上关键字explain/desc
    EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件;
  • 关键字段解析:

    • id:查询的执行顺序。select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)。
    • select_type:表示SELECT的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或子查询)、PRIMARY(主查询,即外层的查询)、UNIONUNION中的第二个或者后面的查询语句)、SUBQUERYSELECT/WHERE之后包含了子查询)等。
    • table:查询涉及的表。
    • type:表示连接类型,性能由好到差的连接性能类型为NULL(查询时不访问任何表)、system(访问一张系统表)、const(根据主键或唯一索引进行访问新表)、eq_refref(使用非唯一性的索引进行查询)、rangeindex(遍历整个索引数进行索引扫描)、ALL(全表扫描)
    • possible_key:显示可能应用在这张表上的索引,一个或多个。
    • key:实际用到或使用的索引,如果为NULL,则没有使用索引。
    • key_len:表示索引中使用的字节数,改值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
    • rows:扫描的行数。MySQL认为必须要执行查询的行数,在innodb引擎表中,是一个估计值,可能并不总是准确的。
    • filtered:表示返回结果的行数占需读取行数的百分比,filtered的值越大越好。
    • extra:额外信息,表示在执行查询的过程中,在前面几个字段当中没有展示出来的值,如 Using index(使用覆盖索引)。

2.6 索引使用规则

2.6.1 验证索引效率
  • 针对字段创建索引:

    sql 复制代码
    create index idx_name on table_name(column_name)
  • 使用 EXPLAIN 查看索引是否生效:

    sql 复制代码
    EXPLAIN SELECT * FROM table_name WHERE column_name = 'value';
  • 观察 key 字段,确保索引被使用。

2.6.2 最左前缀法则

如果索引引用了多列(联合索引),要遵循最左前缀法则。最左前缀法则指的是查询从索引最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。

  • 最左前缀法则,复合索引生效的前提是 查询条件必须从索引的最左列开始,且最左列字段必须存在,但和查询存放的位置无关

    sql 复制代码
    CREATE INDEX idx_name ON table_name(col1, col2, col3);
    
    SELECT * FROM table_name WHERE col1 = ? AND col2 = ? AND col3 = ?; -- 能用索引col1、col2和col3
    
    SELECT * FROM table_name WHERE col1 = ? AND col2 = ?; -- 能用索引col1、col2
    
    SELECT * FROM table_name WHERE col2 = ? AND col3 = ?; -- 不使用索引,进行全表扫描
    
    SELECT * FROM table_name WHERE col2 = ?; -- 不使用索引,进行全表扫描
    
    SELECT * FROM table_name WHERE col1 = ? AND col3 = ?; -- 能用索引col1
    
    SELECT * FROM table_name WHERE col2 = ? AND col3 = ? AND col1 = ?; -- 能用索引col1和col2和col3
  • 范围查询,联合索引中,出现范围查询(>,<),范围查询右侧的列表索引失效

    sql 复制代码
    CREATE INDEX idx_name ON table_name(col1, col2, col3);
    
    SELECT * FROM table_name WHERE col1 = ? AND col2 > ? AND col3 = ?; -- 能用索引col1、col2,因为col2使用了范围查询(>,<)
    
    SELECT * FROM table_name WHERE col1 = ? AND col2 >= ? AND col3 = ?; -- 能用索引col1、col2和col3,因为>=、<=的范围查询不影响
2.6.3 索引失效情况

索引可能会失效的情况:

  1. 对索引进行运算操作,如果对索引进行运算操作,索引将失效。

  2. 字符串不加引号 ,字符串类型字段使用时,不加引号,数据类型不匹配,如 VARCHAR 类型字段用 INT 查询。

    sql 复制代码
    -- 假设col3是字符串,但是这里写成了数值
    SELECT * FROM table_name WHERE col1 = ? AND col2  ? AND col3 = 0; -- 能用索引col1、col2,因为col3没有加''
  3. 使用 OR 关键字连接的条件 ,用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。

    sql 复制代码
    -- 假设col1有索引,但是col2没有索引
    SELECT * FROM table_name WHERE col1 = ? OR col2  ?; -- 索引都失效,因为col2没有索引

    解决办法:将or后的条件中的列建立索引即可。

  4. 对索引列进行函数操作 ,如 WHERE LEFT(column, 3) = 'abc'

  5. 模糊出查询 ,使用 like% 通配符,如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失败。

    sql 复制代码
    SELECT * FROM table_name WHERE col1 like 'word%'; -- 能使用索引
    
    SELECT * FROM table_name WHERE col1 like '%word'; -- 索引失效
    
    SELECT * FROM table_name WHERE col1 like '%word%'; -- 索引失效
  6. 数据分布影响,如果MySQL评估使用索引比全表更慢,则不使用索引。

  7. 隐式转换 ,如 WHERE column = 123,而 columnVARCHAR 类型。

2.6.4 SQL 提示

SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

  • USE INDEX:(建议MySQL使用idx_name为索引,但是决定权在MySQL,可使用,可不使用)

    sql 复制代码
    SELECT * FROM table_name USE INDEX(idx_name) WHERE column = 'value';
  • IGNORE INDEX:(忽略idx_name索引,走别的索引)

    sql 复制代码
    SELECT * FROM table_name IGNORE INDEX(idx_name) WHERE column = 'value';
  • FORCE INDEX:(强制使用idx_name索引,必须使用)

    sql 复制代码
    SELECT * FROM table_name FORCE INDEX(idx_name) WHERE column = 'value';
2.6.5 覆盖索引 & 回表查询
  • 覆盖索引 :所有查询字段都在索引中,避免回表。尽量使用覆盖索引(查询使用了索引,而且需要返回的列,在该索引中已经全部能够找到),减少使用select *

    查看性能时查看Extra字段信息,如果显示使用的是Using where; Using index则性能高,如果显示Using index condition则性能低。

    • Using where; Using index:查找使用了索引,但需要的数据都在索引列中能找到,索引不需要回表查询。
    • Using index condition:查找使用了索引,但需要回表查询数据。
  • 回表查询:索引中没有查询的字段,需要回到主表查找。回表 就是先从二级索引(辅助索引)中去查,查到查询结果(id)后,再根据查询到的结果(id)到聚集索引当中去加载这一行数据。回表查询性能较低。

    sql 复制代码
    CREATE INDEX idx_name ON table_name(col1, col2);
    
    SELECT col1, col2 FROM table_name WHERE col1 = 'value'; -- 覆盖索引
    
    SELECT col2 FROM table_name WHERE col1 = 'value'; -- 覆盖索引
    
    SELECT col1, col3 FROM table_name WHERE col1 = 'value'; -- 需要回表查询
    
    SELECT col1, col2, col3 FROM table_name WHERE col1 = 'value'; -- 需要回表查询
2.6.6 前缀索引

对长字符串列创建索引时,可使用前缀索引提高效率。

  • 语法

    sql 复制代码
    CREATE INDEX idx_name ON table_name(column(n)); -- n表示选取该列字符串的前n个字符串作为索引
  • 前缀长度

    可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

    sql 复制代码
    -- 先对col1进行去重,再求取col1的不重复数据的数量
    SELECT count(distinct col1) FROM table_name; 
    
    -- 计算col1的选择性
    SELECT count(distinct col1)/ count(*) FROM table_name; -- 若等于1,则性能是最好的
    
    -- 截取col1中数据的前5个字符计算期选择性
    SELECT count(distinct substring(col1,1,5))/ count(*) FROM table_name; -- 若等于1,则性能是最好的
2.6.7 单列索引 vs 联合索引
  • 单列索引:即一个索引只包含单个列,适用于单一字段查询。

  • 联合索引 :即一个索引包含了多个列,适用于多个字段组合查询,需符合 最左前缀法则,所以在创建联合索引时需考虑其创建顺序。

    在业务场景中,如果存在多个查询条件,考虑针对查询字段建立索引时,建议建立联合索引,而非单列索引。

    sql 复制代码
    CREATE INDEX idx_name ON table_name(col1, col2);

2.7 索引设计原则

  1. 适当建立索引,避免过多索引影响写入性能。尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
  2. 优先使用 B+ 树索引,避免哈希索引的局限性。
  3. 单表索引数控制在 5~7 个,避免影响性能。
  4. 选择合适的索引列
    • 针对数据量较大时,高频查询列优先建立索引;
    • 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引;
    • 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高;
    • 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引;
  5. 避免冗余索引,复合索引可以代替多个单列索引。要控制索引的数量,索引并不是越多越好,索引越多,维护索引结构的代价也就越大,会影响增删改查的效率。
  6. 如果索引列不能存储NULL ,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。

3. SQL 优化

3.1 插入数据优化

优化 INSERT 语句的方法:

  1. 使用批量插入

    sql 复制代码
    INSERT INTO table_name (col1, col2) VALUES (1, 'A'), (2, 'B'), (3, 'C');
  2. 手动提交事务

    sql 复制代码
    start transaction;
    INSERT INTO table_name (col1, col2) VALUES (1, 'A'), (2, 'B'), (3, 'C');
    INSERT INTO table_name (col1, col2) VALUES (1, 'A'), (2, 'B'), (3, 'C');
    INSERT INTO table_name (col1, col2) VALUES (1, 'A'), (2, 'B'), (3, 'C');
    commit;
  3. 主键顺序插入

    sql 复制代码
    主键乱序插入:8 1 9 21 88 2 4 15 89 5 7 3
    主键顺序插入:1 2 3 4 5 7 8 9 15 21 88 89
  4. 使用 LOAD DATA INFILE 进行高效导入大批量数据

    sql 复制代码
    -- 客户端连接服务器时,加上参数 --local-infile
    mysql --local-infile -u root -p;
    
    -- 查看全局参数local_infile的默认开关是否打开
    select @@local_infile;
    
    -- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
    set global local_infile = 1;
    
    -- 执行load指令将准备好的数据,加载到表结构中
    load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ',' lines terminated by '\n'; -- 主键顺序插入性能高于乱序插入
  5. 禁用索引(适用于大批量插入):

    sql 复制代码
    ALTER TABLE table_name DISABLE KEYS;
    -- 批量插入数据
    ALTER TABLE table_name ENABLE KEYS;

3.2 主键优化

  • 数据组织方式

    在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表 (index organized table IOT)。

    • 页分裂

      页可以为空,也可以填充一半,也可以填充100%。每个页包含了2-N行数据(如果一行数据多大,会行溢出),根据主键排列。

      • 主键顺序插入:随着主键一个一个插入,如果一个页满了,则开启另一个页继续顺序插入。

      • 主键乱序插入:

        1. 如果要插入的主键(50)在一个已经满了的页1(1 5 9 23 47)当中

      ​ 2. 会先开辟一个新的数据页,再找到满的数据页1当中50%(一半)的位置

      ​ 3. 将超出50%的主键(23 47)插入到新开的数据页中

      ​ 4. 再将需要插入的主键(50),插入到已插入的新数据页后(23 47 50)

      ​ 5. 最后重新设置链表指针

      这种现象叫做叶分列。

  • 页合并

    当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变的允许被其他记录声明使用。

    当页中删除的记录达到MERGE_THRESHOLD(默认页的50%),InoDB会开始寻找靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。

    当删除的数据超过50%,如下图数据页2当中假设删除了13、14、15、16,则InoDB会开始寻找靠近的页(页1和页3),页1不能合并,则找页3。

    页3可以合并,则将页3中的数据合并到页2当中

    若后面需要插入数据(id大于页2中的数据)时,继续页3插入即可

    MERGE_THRESHOLD:页合并的阈值,可以自己设置,在创建表或者创建索引时指定。

  • 主键设计原则

    • 满足业务需求的情况下,尽量降低主键的长度且避免主键频繁更新。

    • 插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。如果是乱序插入可能会出现页分裂现象。

    • 尽量不要使用UUID做主键或者是其他自然主键,如身份证号。因为每一次生成的UUID是无序的,在插入时可能会乱序插入就存在页分裂现象,且UUID、身份证号的长度会比较长,在检索时会消耗大量的磁盘IO,存储时也需要更多存储空间。

    • 业务操作时,避免对主键的修改。

3.3 ORDER BY 优化

  1. Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫FileSort排序。
  2. Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率高。
  • 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
  • 尽量使用覆盖索引。
  • 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。
  • 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256k)。

3.4 GROUP BY 优化

  • 在分组操作时,可以通过索引来提高效率。
  • 在分组操作时,索引的使用也是满足最左前缀法则的。

3.5 LIMIT 优化

一个常见又非常头疼的问题就是limit 2000000,10,此时需要MySQL排序前2000010记录,仅仅返回2000000- 2000010的记录,其他记录丢弃,查询排序的代价非常大。

优化思路:

  1. 使用索引进行分页

    sql 复制代码
    SELECT * FROM table_name WHERE id > 1000 LIMIT 10;
  2. 避免 LIMIT M, N 导致全表扫描,采用子查询优化。一半分页查询时,通过创建覆盖索引能够比较好的提高性能,可以通过覆盖索引加子查询形式进行优化。

    sql 复制代码
    SELECT * FROM table_name1 tb1,(SELECT id FROM table_name ORDER BY id LIMIT 2000000,10) tb
    2 WHERE tb1.id = tb2.id; -- 子查询也可以当做一张表
    
    SELECT * FROM table_name WHERE id >= (SELECT id FROM table_name ORDER BY id LIMIT 100000,1) LIMIT 10;

3.6 COUNT 优化

  • MyISAM引擎把一个表的总行存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高(前提条件为查询条件后面没有任何where查询);
  • InnoDB引擎就麻烦了,它执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。

优化思路:自己计算。

  1. 避免 COUNT(\*) 统计整张表数据,可以改为:

    sql 复制代码
    SELECT COUNT(1) FROM table_name;
  2. 存储总记录数:可在独立表维护计数器,每次插入/删除时更新。

  • count()的几种用法

    • count()是一个聚合函数,对于返回的结果集,一行一行地判断,如果count函数的参数不是NULL,累计值就加1,否则不加1,最后返回累计值。

    • 用法:

      • count(*)

        InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。

      • count(主键)

        InnoDB引擎会遍历整张表,把每一行的主键id值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为null)。

      • count(字段)

        没有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。

        not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接进行累加。

      • ``count(1)`

        InnoDB引擎会遍历整张表,但不取值。服务层对于返回的每一行,放一个数字"1"进去,直接按行进行累加。

      按照效率排序的话,count(字段)<count(主键id)<count(1)count(*),所以尽量使用count(*)

3.7 UPDATE 优化

  1. InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。

  2. 尽量使用 PRIMARY KEYUNIQUE KEY 进行更新,避免全表扫描。

  3. 减少更新字段数量

    sql 复制代码
    UPDATE table_name SET col1 = 'new_value' WHERE id = 100;
  4. 避免频繁更新索引列,因更新索引列会导致索引重建,降低性能。


4. 视图、存储过程、触发器

4.1 视图(VIEW)

视图(VIEW)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。

通俗来讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。

4.1.1 作用
  • 封装复杂查询,提高可读性和安全性。
  • 简单。视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。
  • 安全。数据库可以授权,但不能授权到数据库特定行上。通过视图用户只能查询和修改他们所能看见到的数据。
  • 数据独立。视图可以帮助用户屏蔽真实表结构变化带来的影响。
4.1.2 语法
sql 复制代码
-- 创建视图
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT 语句 [WITH[CASCADED | LOCAL] CHECK OPTION];	-- WITH CASCADED CHECK OPTION:检查选项。检查插入视图中的数据是否满足SELECT 语句 中的条件。

-- 查询视图
SHOW CREATE VIEW 视图名称;

-- 查看视图数据
SELECT * FROM 视图名称 ......;

-- 修改视图
法1:CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT 语句 [WITH[CASCADED | LOCAL] CHECK OPTION];
法2:ALTER VIEW 视图名称[(列名列表)] AS SELECT 语句 [WITH[CASCADED | LOCAL] CHECK OPTION];

-- 删除视图
DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ...
4.1.3 示例
sql 复制代码
-- 创建视图
CREATE VIEW user_view AS SELECT id, name, email FROM users WHERE status = 'active';

-- 查询视图
SHOW CREATE VIEW user_view;

-- 查询视图数据
SELECT * FROM user_view;
SELECT * FROM user_view WHERE id < 3;

-- 修改视图
CREATE OR REPLACE VIEW user_view AS SELECT id, name, email, addr FROM users WHERE status = 'active';
ALTER VIEW user_view AS SELECT id, name, email, addr FROM users WHERE status = 'active';

-- 删除视图
DROP VIEW IF EXISTS user_view;
4.1.4视图的检查选项

当使用 WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如插入、更新、删除,以使其符合视图的定义。MySQL允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查的规范,MySQL提供了两个选项:CASCADED LOCAL,默认值为CASCADED

  • CASCADED :只有在当前视图的WHERE子句中定义的条件会在插入或更新操作时进行检查。换句话说,只有直接影响到这个视图的行条件会被考虑,而不会检查外层视图的条件。

  • LOCAL:在执行插入或更新操作时,系统不仅会检查当前视图的条件,还会递归地检查所有上层视图的条件。这样确保了数据的完整性,符合所有相关的筛选条件。

4.1.5 视图的更新

要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:

  1. 聚合函数或窗口函数(SUM()MIN()MAX()COUNT()等)
  2. DISINCT
  3. GROUP BY
  4. HAVING
  5. UNION或者UNION ALL

4.2 存储过程(Stored Procedure)

4.2.1 介绍:
  • 存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和服务器之间的传输,对外提高数据处理的效率是有好处的。、

  • 存储过程思想上很简单,就是数据库SQL语言层的代码封装与重用。

  • 特点:

    • 封装、复用
    • 可以接收参数,也可以返回数据
    • 减少网络交互,效率提升
  • 作用:封装 SQL 逻辑,减少网络传输。

4.2.2 语法
  • 创建

    sql 复制代码
    CREATE PROCEDURE 存储过程名称([参数列表])
    BEGIN
      -- SQL语句
    END; 
  • 调用

    sql 复制代码
    CALL 名称([参数])
  • 查看

    sql 复制代码
    SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_ACHEMA = '数据库名称'; -- 查看指定数据库的存储过程及状态信息
    
    SHOW CREATE PROCEDURE 存储过程名称; -- 查询某个存储过程的定义
  • 删除

    sql 复制代码
    DROP PROCEDURE [IF EXISTS] 存储过程名称;
4.2.3 示例
sql 复制代码
-- 创建存储过程
CREATE PROCEDURE GetUser(IN user_id INT)
BEGIN
  SELECT * FROM users WHERE id = user_id;
END;

-- 调用
call GetUser(6)

注意:在命令行中,执行创建存储过程的SQL时,需要通过关键字delimiter指定SQL语句的结束符。

4.2.4 变量
  • 系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL )、会话变量(SESSION)。默认为会话变量。

    • 查看系统变量

      sql 复制代码
      SHOW [SESSION | GLOBAL] VARIABLES; -- 查看所有系统变量,默认为会话变量。
      
      SHOW [SESSION | GLOBAL] VARIABLES LIKE '......'; -- 可以通过LIKE模糊匹配方式查找变量
      
      SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值
    • 设置系统变量

      sql 复制代码
      SET [SESSION | GLOBAL] 系统变量名 = 值; -- 0为关闭,1为开启
      
      SET @@[SESSION | GLOBAL]系统变量名 = 值;

      注意:

      • 如果没有指定SESSION/GLOBAL,默认会是SESSION,会话变量。
      • MySQL服务器重启后,所设置的全局参数会失效,要想不失效,可以在/etc/my.cnf中配置。
  • 用户自定义变量 是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用@变量名使用就可以。其作用域为当前连接。

    • 赋值

      sql 复制代码
      SET @var_name = expr[,@var_name = expr]...;
      
      SET @var_name := expr[,@var_name := expr]...;
      
      SELECT @var_name := expr[,@var_name := expr]...;
      
      SELECT 字段名 INTO @var_name FROM 表名;
    • 使用

      sql 复制代码
      SELECT @var_name;
    • 注意:用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL

  • 局部变量 是根据要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN...END块。

    • 声明

      sql 复制代码
      DECLARE 变量名 变量类型 [DEFAULT ...];

      变量类型就是数据库字段类型:INT、BEGINT、CHAR、VARCHAR、DATE、TIME等。

    • 赋值

      sql 复制代码
      SET 变量名 = 值;
      SET 变量名 := 值;
      SELECT 字段名 INTO 变量名 FROM 表名 ...;
4.2.5 if
  • 语法

    sql 复制代码
    IF 条件1 THEN
    	......
    ELSEIF 条件2 THEN  -- 可选
    	......
    ELSE 			  -- 可选
    	......
    END IF;
  • 参数

    • IN:该类参数作为输入,也就是需要调用时传入值,默认。
    • OUT:该类参数作为输出,也就是该参数可以作为返回值。
    • INOUT:即可作为输入参数,也可作为输出参数。
    sql 复制代码
    CREATE PROCEDURE 存储过程名称([IN/OUT/INOUT 参数名 参数类型])
    BEGIN
      -- SQL语句
    END; 
4.2.6 case
  • 语法一

    sql 复制代码
    CASE case_value
    	WHEN when_value1 THEN statement_list1
    	[WHEN when_value2 THEN statement_list2]...
    	[ELSE statement_list]
    END CASE;
  • 语法二

    sql 复制代码
    CASE
    	WHEN search_condition1 THEN statement_list1
    	[WHEN search_condition2 THEN statement_list2]...
    	[ELSE statement_list]
    END CASE;
4.2.7 while

while循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。具体语法为:

sql 复制代码
# 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
WHILE 条件 DO 
	SQL逻辑...
END WHILE;
4.2.8 repeat

repeat是有条件的循环控制语句,当满足条件的时候退出循环。具体语法为:

sql 复制代码
# 先执行一次逻辑,然后判定逻辑是否满足,如果满足,则退出。如果不满足,则继续下一次循环
REPEAT
	SQL逻辑...
	UNTIL 条件
END REPEAT;
4.2.9 loop

LOOP实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。LOOP可以配合一下两个语句使用:

  • leave:配合循环使用,退出循环。
  • ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,之间进入下一次循环。
sql 复制代码
[begin_label:] LOOP
	SQL逻辑...
END LOOP [end_label];
sql 复制代码
LEAVE label; -- 退出指定标记的循环体
ITERATE label; -- 直接进入下一次循环体
4.2.10 游标

**游标(CURSOR)**是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的declareOPENFETCHCLOSE,其语法分别如下。

  • 游标声明

    sql 复制代码
    DECLARE 游标名称 CURSOR FOR 查询语句;
  • 打开游标

    sql 复制代码
    OPEN 游标名称;
  • 获取游标记录

    sql 复制代码
    FETCH 游标名称 INTO 变量[,变量];
  • 关闭游标

    sql 复制代码
    CLOSE 仪表名称;
4.2.11 条件处理程序

条件处理程序(Handler):可以用来定义在流程控制结构执行过程中遇到的问题时相应的处理步骤。具体语法为

sql 复制代码
DECLARE handler_action HANDLER FOR condition_value [condition_value] ... statement;

handler_action
	CONTINUE :继续执行当前程序(忽略错误)
	EXIT:终止执行当前程序(相当于抛出异常)
condition_value
	SQLSTATE sqlstate_value:状态码,如 '23000'(违反唯一约束)
	SQLWARNING:所有以01开头的SQLSTATE代码的简写,捕获所有警告
	NOT FOUND:所有以02开头的SQLSTATE代码的简写,通常用于游标处理,表示未找到匹配的数据。
	SQLEXCEPTION:所有没有被SQLWARNING或NOT FOUND获取的SQLSTATE代码的简写
statement
	执行的 SQL 语句或 BEGIN ... END 代码块

​ 可参考MySQL官网的状态码说明:https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html

4.3 存储函数

存储函数是有返回值的存储过程,存储函数的参数只能是INT类型的。具体语法如下:

sql 复制代码
CREATE FUNCTION 存储函数名称([参数列表])
RETURNS type [characteristic ...]
BEGIN
	--SQL语句
	RETURN ...;
END;

characteristic说明:
	DETERMINISTIC:相同的输入参数总是产生相同的结果。
	NOT SQL:不包含SQL语句。
	READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句。

4.4 触发器(Trigger)

  • 介绍

    • 触发器是与表有关的数据库对象,指在insert/update/delete之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。
    • 使用别名OLDNEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。
    触发器类型 NEWOLD
    INSERT型触发器 NEW表示将要或者已经新增的数据
    UPDATE型触发器 OLD表示修改之前的数据,NEW表示将要或已经修改后的数据
    DELETE型触发器 OLD表示将要或者已经删除的数据
  • 作用:自动执行 SQL 逻辑,如数据校验、日志。

  • 语法

    sql 复制代码
    -- 创建
    CREATE TRIGGER trigger_name
    BEFORE/AFTER INSERT/UPDATE/DELETE 
    ON table_name FOR EACH ROW -- 行级触发器
    BEGIN
    	trigger_stmt;
    END;
    
    -- 查看
    SHOW TRIGGERS;
    
    -- 删除
    DROP TRIGGER [schema_name.]trigger_name; -- 如果没有指定schema_name,默认为当前数据库。

5. 锁(Locks)

  • 介绍

    ​ 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算机资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤为重要,页更加复杂。

  • 分类

    MySQL中的锁,按照锁的颗粒度分,分为以下三类:

    1. 全局锁:锁定数据库中的所有表。
    2. 表级锁:每次操作锁住整张表。
    3. 行级锁:每次操作锁住对应的行数据。

5.1 全局锁(Global Lock)

5.1.1 全局锁介绍

全局锁是指 锁住整个 MySQL 数据库实例 ,加锁后整个示例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞,用以阻止其他线程对数据库执行 读写操作。主要用于:

  • 备份数据 (如 FLUSH TABLES WITH READ LOCK)。其最典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
  • 数据库迁移
  • 一致性快照
5.1.2 加全局锁
sql 复制代码
-- 加全局锁
FLUSH TABLES WITH READ LOCK;
  • 作用:

    • 让所有表进入 只读模式,避免写操作。
    • 当前会话仍可读写临时表,其他会话只能读取表数据,不能写入。
    • 适用于 MyISAM 存储引擎 备份,避免写入数据时不一致。
  • 特点:

    数据库中加全局锁,是一个比较重的操作,存在以下问题:

    1. 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得暂停。
    2. 如果在从主库上备份,按摩在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟
5.1.3 释放全局锁

释放全局锁的方式:

sql 复制代码
UNLOCK TABLES;
  • 其他释放方式:
    • 会话断开 (如 exit)。
    • 事务提交 (如果事务处于 autocommit=0)。
5.1.4 一致性数据备份
(1)MyISAM 存储引擎:

MyISAM 不支持事务 ,为了避免在备份期间数据不一致,通常先获取 全局锁

sql 复制代码
FLUSH TABLES WITH READ LOCK;

然后复制数据文件(.frm, .MYD, .MYI)。

(2)InnoDB 存储引擎:

InnoDB 支持 MVCC(多版本并发控制) ,可以使用 事务的快照读 进行备份,而无需全局锁:

sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION WITH CONSISTENT SNAPSHOT;

然后使用 mysqldump 进行备份:

sql 复制代码
mysqldump -u root -p --single-transaction mydb > mydb.sql
  • --single-transaction :在 REPEATABLE READ 级别开启事务,保证备份的一致性,不锁表。

5.2 表级锁(Table-Level Lock)

表级锁锁定 整张表,可分为:

  • 表锁(Table Lock):显式或隐式锁定整个表。
  • 元数据锁(MDL, Metadata Lock):保护表结构变更。
  • 意向锁(Intention Lock):用于协调行锁与表锁。
5.2.1 表锁(Table Lock)
  • 表锁分类

    1. 读锁(SHARED, S 锁)
      • 其他线程 只能读,不能写
      • LOCK TABLES table_name READ;
    2. 写锁(EXCLUSIVE, X 锁)
    • 其他线程 不能读,也不能写
    • LOCK TABLES table_name WRITE;
  • 语法

sql 复制代码
LOCK TABLES 表名 READ/WRITE;   -- 读锁、加锁,只允许 SELECT
UNLOCK TABLES;            -- 释放锁,客户端断开连接

LOCK TABLES users WRITE;  -- 写锁,不允许其他事务访问
UNLOCK TABLES;
5.2.2 元数据锁(MDL, Metadata Lock)

​ MDL加锁过程是系统自动控制,无需显示使用,在访问一张表的时候会自动加上。MDL锁主要作用的维护表元数据的数据唯一性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。

  • 作用

    • 保护 表结构变更 ,防止 ALTER TABLEDROP TABLE 与正在执行的事务冲突。

    • MySQL 5.5+ 引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行表更操作的时候,加上MDL写锁(排他)。

对应SQL 锁类型 说明
lock tables xxx read/write SHARED_READ_ONLY/SHARED_NO_READ_WRITE
select、select...lock in share mode SHARED_READ SHARED、SHARED_WRITE兼容,与EXCLUSIVE互斥
insert、update、delete、select ... for update SHARED_WRITE SHARED、SHARED_WRITE兼容,与EXCLUSIVE互斥
alter table ... EXCLUSIVE 与其他的MDL都互斥
  • MDL 示例
sql 复制代码
SELECT * FROM users;       -- 事务A获取 MDL 读锁
ALTER TABLE users ADD COLUMN age INT; -- 事务B 等待 MDL 释放

-- 查看元数据锁(系统表中的metadata_locks表):
select object_type,object_schema,object_name,lock_type_lock,lock_duration from performance_schema.metadata_locks;
  • 避免 MDL 死锁:

    • 短事务优先:事务不应长时间持有 MDL。

    • SET innodb_lock_wait_timeout = 5; 设定等待时间。

5.2.3 意向锁(Intention Lock)

意向锁是 InnoDB 自动管理 ,用于协调表锁与行锁,加快大范围锁检查。

  • 意向锁类型

    1. 意向共享锁(IS, Intention Shared)

      • 事务 想要 获取某行的 共享锁(S),但不会锁全表。
      • SELECT ... LOCK IN SHARE MODE;添加。
      • 与共享锁(read)兼容,与表锁排他锁(write)互斥
    2. 意向排他锁(IX, Intention Exclusive)

      • 事务 想要 获取某行的 排他锁(X),但不会锁全表。
      • SELECT 、UPDATE、DELETE、INSERT ... FOR UPDATE;添加。
      • 与共享锁(read)及排他锁(write)都互斥。意向锁之间不会互斥。
  • 意向锁测试

sql 复制代码
SET autocommit = 0;

START TRANSACTION;
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- 事务A获取 IS 锁

SELECT * FROM users WHERE id = 1 FOR UPDATE;  -- 事务B 等待 IS 释放 IX

-- 查看意向锁及行锁的加锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
  • LOCK IN SHARE MODE(S 锁) 产生 IS 锁
  • FOR UPDATE(X 锁) 产生 IX 锁,会阻塞共享锁。

5.3 行级锁(Row-Level Lock)

​ 行级锁,每次操作锁住对应的行数据。锁定粒度最小 ,发送锁冲突的概率最低,并发度最高。支持高并发,在InnoDB 引擎中使用。

​ InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:

  • 行锁(Record Lock) :锁定单个行记录的锁,防止其他事务对此行进updatedelete。在RC、RR隔离级别下都支持。

  • 间隙锁(Gap Lock) :锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。

  • 临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。

5.3.1 行锁(Record Lock)

InnoDB实现了一下两种类型的行锁:

  1. 共享锁(S):允许一个事务去读一行,阻止其他事物获得相同数据集的排他锁。
  2. 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
\请求锁类型 当前锁类型\ S(共享锁) X(排他锁)
S(共享锁) 兼容 冲突
X(排他锁) 冲突 冲突
SQL 行锁类型 说明
INSERT ... 排他锁 自动加锁
UPDATE ... 排他锁 自动加锁
DELETE ... 排他锁 自动加锁
SELECT(正常) 不加任何锁
SELECT ... LOCK IN SHARE MODE 共享锁 需要手动在SELECT之后加LOCK IN SHARE MODE
SELECT ... FOR UPDATE 排他锁 需要手动在SELECT之后加FOR UPDATE

默认情况下,InnoDB在REPEATABLE READ事务隔离级别运行,InnoDB使用next_key锁进行搜索和索引扫描,以防止幻读。

  1. 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
  2. InnoDB的行锁是针对索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁

行锁仅锁定符合查询条件的行 ,事务执行时 自动加锁

sql 复制代码
SELECT * FROM users WHERE id = 1 FOR UPDATE;

-- 查看意向锁及行锁的加锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
  • 防止其他事务修改 id=1 的数据。
5.3.2 间隙锁(Gap Lock)和临键锁(Next-Key Lock)

默认情况下,InnoDB在REPEATABLE READ事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读。

  1. 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
  2. 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next_key退化为间隙锁。
  3. 索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。

注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙锁上采用间隙锁。

锁住某范围内"不存在的行" ,防止幻读

sql 复制代码
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;
  • 如果 id=15 不存在 ,也会被锁定,避免其他事务插入 id=15

行锁 + 间隙锁的组合 ,防止幻读

sql 复制代码
SELECT * FROM users WHERE id = 10 FOR UPDATE;;
  • 锁定 id=10 及其 前后范围9 < id ≤ 10)。

避免间隙锁

使用 innodb_locks_unsafe_for_binlog = 1

sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  • READ COMMITTED 级别不使用间隙锁,提高并发性。

6. InnoDB 引擎

6.1 InnoDB 的逻辑存储结构

InnoDB 将数据以逻辑页面(page)和扩展(extent)的形式组织,并存储在表空间(tablespace)中。其主要组成部分包括:

  • 表空间(Tablespace)
    • 一个MySQL实例可以对应多个表空间,用于存储记录、索引等数据。
    • 系统表空间(System Tablespace) :默认存储在一个或多个共享数据文件(如 ibdata1)中,存储系统数据、数据字典、undo log 等。
    • 文件单表模式(File-Per-Table):每个 InnoDB 表可以存储在独立的 .ibd 文件中,便于管理和空间回收。
    • 分为数据段(Leaf node segment)、索引段(Non-leaf-segment)、回滚段(Rollback-segment),InnoDB是索引组织表,数据段就是B+树的叶子结点,索引段即为B+树的非叶子节点。
    • 段用来管理多个Extent(区)。
  • 扩展(Extent)也叫区
    • 由 64 个连续的页组成,用于存储大量数据时的连续空间分配。
    • 表空间的单元结构,每个区的大小为1M。默认情况下,InnoDB存储引擎大小为16K,即一个区中一共有64个连续的页。
  • 逻辑页(Page)
    • 数据在磁盘上以页(通常为 16KB)为InnoDB存储引擎磁盘管理的基本单位组织也是最小单元,每个页内部又包含页头、记录区和页尾校验码。
    • 页中记录采用紧凑格式存储,同时页内部记录按照主键顺序排列,形成 B+Tree 索引。
    • 为了保证页的连续性,InnoDB存储引擎每次从磁盘申请4-5个区。
    • InnoDB存储引擎数据是按行进行存放的。
    • Trx_id:每次对某条数据进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
    • Roll_pointer:每次对某条引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

6.2 InnoDB 的架构

MySQL5.5版本开始,默认使用InnoDB存储引擎,它擅长事务处理,具有崩溃性回复特性,在日常开发中使用非常广泛。InnoDB 的架构分为内存结构(左侧)和磁盘结构(右侧),同时有多种后台线程负责维护和管理。

6.2.1 内存结构

InnoDB 的内存结构主要包括以下部分:

  • 缓冲池(Buffer Pool)

    • 缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,再执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。
    • 大部分读写操作直接在缓冲池中进行,极大地提高了 I/O 效率。
    • 核心组件,用于缓存磁盘上的数据页、索引页以及部分内部数据结构。缓冲层以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:
      • free page:空闲page,未被使用。
      • clean page:被使用page,数据没有被使用过。
      • dirty page:脏页,被使用page,数据被修改过,页中数据与磁盘的数据产生了不一致。
  • 变更缓冲(Change Buffer,又称 Insert Buffer或插入缓冲区)

    • 针对于非唯一二级索引页,再执行DML语句时,如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区Change Buffer中,在未来数据被读取时,再将数据合并回复到Buffer Pool中,再将合并后的数据刷新到磁盘中。

    • 缓存二级索引页的变更操作,以延后或合并 I/O 写入。

    • Change Buffer 的意义是什么?

      ​ 与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引数中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了Change Buffer之后,我们可以再缓冲池中进行合并处理,减少磁盘IO。

  • 自适应哈希索引(Adaptive Hash Index)

    • 用于对Buffer Poll数据的查询。InnoDB存储引擎会监控对表上各索引页的查询,如果观察到hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。
    • 在热点数据上自动生成哈希索引,无需人工干预,是系统根据情况自动完成,以提升查询性能。
    • 参数adaptive_hash_index
  • 日志缓冲区(Log Buffer)

    • 用来保存要写入到磁盘中的log日志数据(redo log、undo log),默认大小为16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘I/O。
    • 用于暂存事务产生的 redo log 信息,待一定条件满足(如事务提交、日志缓冲区满、定时刷新)后写入磁盘上的 redo log 文件。
    • 减少了磁盘 I/O 次数,提高写入性能。
    • 参数:
      • innodb_log_buffer_size:缓冲区大小。
      • innodb_flush_log_at_trx_commit:日志刷新到磁盘时机。
        • 1:日志在每次事务提交时写入并刷新到磁盘。
        • 0:每秒将日志写入并刷新一次。
        • 2:日志在每次事务提交后写入,并美妙刷新到磁盘一次。
  • 锁系统(Lock System):管理行锁、表锁等并发控制信息。

6.2.2 磁盘结构

磁盘结构则主要涉及以下文件和区域:

  • 系统表空间文件(System Tablespace)

    • 系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建的,它也可能包含表和索引数据。(在MySQL5.x版本中还包含InnoDB数据字典、undolog等)
    • 参数innodb_data_file_path
    • 存储数据字典、undo log、插入缓冲以及其他共享数据,通常为 ibdata1 文件。
  • 文件单表数据文件(File-per-Tablespaces)

    • 每个表的文件表空间包含单个InnoDB表的数据和索引,并存储在文件系统上的单个数据文件中。
    • 参数innodb_file_per_table
    • 如果启用了 file-per-table 模式,每个表的数据和索引存储在独立的 .ibd 文件中。
  • 通用表空间(General Tablespaces)

    • 需要通过CREATE TABLESPACE语法创建通用表空间,在创建时,可以指定该表空间。

      sql 复制代码
      -- 创建表空间
      CREATE TABLESPACE XXX ADD DATAFILE 'file_name' ENGIN = 'engine_name';
      
      -- 指定关联表空间
      CREATE TABLE XXX ... TABLESPACE ts_name;
  • 撤销表空间(Undo Tablespaces)

    • MySQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16M),用于存储undo log日志。
  • 临时表空间(Temporary Tablespaces)

    • InnoDB使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。
  • 双写缓冲区(Doublewrite Buffer)

    • InnoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。
    • 为了防止部分页写入失败而引起数据损坏,将页先写入连续区域,再写入各自的目的位置。
    • 文件包含:
      • #ib_16384_0.dblwr
      • #ib_16384_1.dblwr
  • 重做日志(Redo Log 文件)

    • 是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲区(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中,用于在刷新脏页到磁盘使,发生错误时,进行数据恢复使用。
    • 以循环方式写入操作日志文件,涉及两个文件:
      • ib_logfile0
      • ib_logfile1。
    • 记录已修改但尚未写入数据页的数据,用于故障恢复。通常由多个文件组成,如 ib_logfile0、ib_logfile1。
  • Undo Log(回滚日志)

    • 用于存储数据修改前的历史版本,以便回滚和支持 MVCC,多数存储在系统表空间中或单独的 undo 表空间中(MySQL 5.6 及以上支持独立 undo 表空间)。
6.2.3 后台线程

InnoDB 内部运行多个后台线程,负责各项维护任务,例如:

  • Master Thread

    • 核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收。
  • IO Thread

    • 在InnoDB存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数据库的性能,而IO Thread主要负责这些IO请求的回调。

      线程类型 默认个数 职责
      Read thread 4 负责读操作
      Write thread 4 负责写操作
      Log thread 1 负责将日志缓冲区刷新到磁盘
      insert buffer thread 1 负责将缓冲区内容刷新到磁盘
  • 后台清除线程(Purge Thread)

    • 清理已提交事务的 undo log,在事务提交之后,undo log可能用不了了,就可以用它来回收,用以维护 undo log 版本链,并回收空间。
  • Page Cleaner Thread

    • 协助Master Thread刷新脏页到磁盘的线程,它可以减轻Master Thread 的工作压力,减少阻塞。
  • 缓冲池刷新线程(Buffer Pool Flush Thread)

    • 定期将脏页刷新到磁盘,保证数据持久性。
  • 日志写入线程(Log Writer Thread)

    • 将日志缓冲中的 redo log 数据写入 redo log 文件,确保事务提交的持久性。
  • 变更缓冲合并线程(Change Buffer Merge Thread)

    • 后台合并变更缓冲中的操作,减少磁盘 I/O。
  • 插入缓冲合并线程

    • 针对插入操作,进行缓冲区的合并操作。

这些线程协同工作,确保高并发下的数据一致性和高效性。

6.3 事务原理

事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。

InnoDB 事务机制基于 ACID 原则,主要依赖 redo log 和 undo log 来保证数据的一致性和恢复能力。

6.3.1 事务概述
  • 事务(Transaction):是一组操作的集合,是一组原子性操作,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,这些请求要么全部成功提交,要么全部回滚,保证数据库状态的一致性。
  • ACID 特性
    • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
    • 一致性(Consistency):事务完成时,必须使所以的数据都保持一种状态。
    • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
    • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久性的。
  • MVCC 支持:通过多版本控制,允许读写并发操作而不产生阻塞。
6.3.2 Redo Log(保证事务持久性)

重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。

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

  • 作用:记录对数据页的修改操作,用于崩溃恢复,确保事务提交后数据不会丢失。
  • 工作流程
    • 修改操作先在内存中进行,并记录对应的 redo log 到日志缓冲区。
    • 事务提交时,日志缓冲中的 redo log 被刷新到磁盘中的 redo log 文件。
    • 系统重启时,通过 redo log 恢复未写入磁盘的数据页。
  • 特性:采用顺序写入,性能较高;与双写缓冲配合防止部分写入导致数据损坏。
6.3.3 Undo Log(保证事务原子性)

​ 回归回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)

undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。

  • Undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。

  • Undo log存储:undo log采用段的方式进行管理和记录,存放在前面介绍的rollback segment回滚段中,内部包含1024个undo log segment。

  • 作用:记录数据修改前的旧值,用于事务回滚和 MVCC 的版本控制。

  • 用途

    • 回滚事务:当事务出错或主动回滚时,利用 undo log 将数据恢复到修改前的状态。
    • 支持 MVCC:保存记录的历史版本,供长事务或快照读操作使用。
  • 存储:Undo log 通常存储在系统表空间或独立的 undo 表空间中,且其内容形成版本链,关联每个数据记录的修改历史。

6.4 MVCC(多版本并发控制)

MVCC 是 InnoDB 提供的一种并发控制机制,使得读操作不会阻塞写操作,从而提高并发性能。主要涉及以下内容:

6.4.1 基本概念
  • 当前读 :读取的记录是最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select ... lock in share mode(共享锁)select ... for updateupdateinsertdelete(排他锁)都是一种当前读。
  • 快照读 :简单的select(不加锁)就是快照读。快照读,读取的是记录数据的可见版本,有可能时历史数据,不加锁,是非阻塞读。
    • Read Committed :每次select,都生成一个快照读。
    • Repeatable read :开启食物后第一个select语句才是快照读的地方。
    • Serilizable:快照读会退化为当前读。
  • MVCC:全称Multi-Version Concurrency Control,多版本并发控制器。指维护一个数据的多个样本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。
  • 多版本控制:每个数据记录保存多个版本,读者可以根据事务开始时的时间戳或 ReadView 来访问"快照"数据,从而避免读取未提交的数据。
  • 非锁定读:读操作无需加锁,通过版本控制实现一致性视图。
6.4.2 隐藏字段与 Undo Log 版本链
  • 隐藏字段:InnoDB 的每条记录都包含三个隐藏列:一个记录创建该版本的事务 ID(trx_id),另一个是回滚指针(roll_ptr),指向 undo log 中保存的前一个版本。

    隐藏字段 含义
    DB_TRX_ID 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID。
    DB_ROLL_PTR 回滚指针,只想这条记录的上一个版本,用于配合undo log,指向上一个版本。
    DB_ROW_ID 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。
  • undo log

    • 回滚日志,在insertupdatedelete的时候产生的便于数据回归的日志。
    • insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
    • updatedelete的时候,产生的undo log日志不仅在回滚的时候需要,在快照读时也需要,不会立即被删除。
  • Undo Log 版本链:

    • 执行事务2:


      执行后(若需要回滚,指针直接可以找到):

    • 执行事务3

      更新后:

    • 开启事务4


      修改后

    • 不同事务或相同事务对同一条记录进行修改,会导致该记录的undo log生成一条记录版本链表,链表的头部是最新的旧记录,链表的尾部是最早的记录。

    • 当记录被更新时,新版本指向旧版本,形成链式结构。这样,每个事务通过自己的 ReadView 可以回溯到合适的记录版本。

6.4.3 读视图(ReadView )的介绍
  • ReadView

    • 快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。

    • 是事务在启动时创建的一个快照,记录当前活动事务的状态。

    • 它包含正在执行的事务 ID 列表、最小活跃事务 ID 等信息。

    • 读操作依据 ReadView 判断记录的版本是否可见,从而实现一致性读取。

    • 包含四个核心字段

      字段 含义
      m_ids 当前活跃的事务ID集合
      min_trx_id 最小活跃事务ID
      max_trx_id 预分配事务ID,当前最大事务ID+1(因为事务ID是自增的)
      create_trx_id ReadView创建者的事务ID
  • 版本链数据访问规则(其中trx_id:代表是当前事务ID)

    1. trx_id == create_trx_id ?可以访问该版本 →成立,说明数据是当前这个事务更改的。
    2. trx_id < min_trx_id ?可以访问该版本 →成立,说明数据已经提交了。
    3. trx_id > max_trx_id ?不可以访问该版本 →成立,说明事务是在ReadView生成后才开启的。
    4. min_trx_id? <= trx_id <= max_trx_id ?如果trx_id不在m_ids中是可以访问该版本的 →成立,说明数据已经提交。
  • 不同的隔离级别,生成ReadView的时机不同

    • READ COMMITTED:在事务中每一次执行快照读时生成ReadView。
    • REPEATABLE READ:仅在事务中第一次执行快照时生成ReadView,后续复用该ReadView。
6.4.4 原理分析(以 RC 隔离级别为例)

读已提交(RC) 隔离级别下,在事务中(每一次执行快照读)每个查询语句都会生成一个新的 ReadView:

  • 读操作
    • 当执行查询时,会生成当前时间点的 ReadView,该 ReadView 列出了所有在查询开始时已经提交的事务以及正在进行的事务。
    • 查询只读取其事务提交时间早于当前 ReadView 中最小值的记录版本(或当前版本为最新且已提交的记录)。
  • 写操作
    • 每次更新都会生成新的记录版本,并在 undo log 中保存旧版本。
    • 其他事务读取时,如果 ReadView 判定某版本不可见,则会"回退"到旧版本,确保查询结果始终读取到已经提交的数据。
  • 效果
    • 保证了每个 SQL 语句都看到一个稳定的数据快照,从而避免了脏读和不可重复读问题。
    • 与 REPEATABLE READ 相比,RC 隔离级别下每条语句拥有最新的快照,减少了因长事务而导致的历史版本积累。
  • RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。

7. MySQL 管理

MySQL 作为广泛使用的开源关系型数据库管理系统,内置了多个系统数据库,用于存储和管理数据库服务器的元数据、权限信息和性能指标等。

7.1系统数据库

MySQL 数据库安装完成后,子代理以下四个数据库,具体作用如下:

  • mysql
    • 存储MySQL服务器正常运行所需要的各种信息(时区、主从、用户、权限等)。
    • 存储核心的权限和配置信息,包括用户账户、权限设置、存储过程、事件定义、日志和时区信息等。
  • information_schema
    • 提供了访问数据库元数据的各种表和视图,包含数据库、表、字段类型及访问权限等。
    • 提供只读视图,包含关于数据库、表、列、权限等的元数据信息。
  • performance_schema
    • 为MySQL服务器运行时状态提供了一个底层监控功能,主要用于收集数据库服务器性能参数。
    • 保存服务器运行过程中的状态信息,用于性能监控,例如统计执行的语句、各阶段耗时、内存使用情况等。
  • sys
    • 包含了一系列方便DBA和开发人员利用performance_schema性能数据库进行性能调优和诊断的视图。
    • 通过视图形式将 information_schemaperformance_schema 的信息结合,方便用户了解服务器的性能指标。

7.2 常用管理工具

为了简化 MySQL 的管理和操作,以下是几款常用的图形化管理工具:

  • mysql

    该mysql不是指mysql服务器,而是指mysql的客户端工具

    sql 复制代码
    -- 语法
    	mysql [options] [database]
    
    -- 选项
    	-u,--user=name			#指定用户名
    	-p,--password[=name]	#指定密码
    	-h,--host=name		    #指定服务器IP或域名
    	-P,--port=port			#指定连接端口
    	-e,--execute=name		#指定执行SQL语句并退出

    -e选项可以再MySQL客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本,这种方式尤其方便。

    sql 复制代码
    -- 示例
    	mysql -uroot -p123456 db01 -e "select * from stu";
  • mysqladmin

    • mysqladmin是一个执行管理操作的客户端程序。可以用它来检查服务器的配置和当前状态,创建并删除数据库等。

      sql 复制代码
      -- 通过帮助文档查看选项:
      	mysqladmin --help
      	
      -- 示例
      	mysqladmin -uroot -p123456 drop 'test01';
      	mysqladmin -uroot -p123456 version;
  • mysqlbinlog

    • 由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文件格式,就会使用到mysqlbinlog日志管理工具。

      sql 复制代码
      -- 语法
      	mysqlbinlog [options] log-file1 log-file2 ...
      
      -- 选项
      	-d,--database=name		#指定数据库名称,只列出指定数据库相关操作
      	-o,--dffset=#			#忽略掉日志中的前n行命令
      	-r,--result-file=name	#将输出的文本格式日志输出到指定文件
      	-s,--short-form			#显示简单格式,省略调一些信息
      	--start-database=date1 --stop--datetime=date2	#指定日期间隔内的所有日志
      	--start-positiion=pos1 --stop-position=pos2	#指定位置间隔内的所有日志
  • mysqlshow

    • mysqlshow客户端对象查找工具,用来很快查找存在哪些数据库、数据库中的表、表中的列或者索引。

      sql 复制代码
      -- 语法
      	mysqlshow [options] [db_name[table_name[col_name]]]
      
      -- 选项
      	-- count	#显示数据库及表的统计信息(数据库、表 均不可以指定)
      	-i			#显示指定数据库或指定表的状态信息
      
      -- 示例
      	# 查询每个数据库的表的数量及表中记录的数量
      	mysqlshow -uroot -p1234 --count
      	
      	# 查询test库中每个表中的字段数,及行数
      	mysqlshow -uroot -p1234 test --count
      	
      	# 查询test库中book表的详细情况
      	mysqlshow -uroot -p1234 test book --count
  • mysqldump

    • mysqldump客户端工具用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,插入表,及插入表的SQL语句。

      sql 复制代码
      -- 语法
      	mysqldump [options] db_name[tables]
      	mysqldump [options] --database/-B dbq [db2 db3 ...]
      	mysqldump [options] --all-databases/-A
      
      -- 连接选项
      	-u, --user=name	#指定用户名
      	-p, --password[=name]	#指定密码
      	-h, --host=name	#指定服务器ip或域名
      	-P, --port=#	#指定连接端口
      	
      -- 输出选项
      	--add-dorp-database	#在每个数据库创建语句前加上drop database语句
      	--add-drop-table	#在每个表创建语句前加上drop table语句,默认开启;不开启(--skip-add-drop-table)
      	-n, --no-create-db	#不包含数据库的创建语句
      	-t, --no-create-info	#不包含数据表的创建语句
      	-d, --no-data	#不包含数据
      	-T, --tab=name	#自动生成两个文件:一个.sql文件,创建表结构的语法;一个.txt文件,数据文件
  • mysqlimport/source

    • mysqlimport是客户端数据导入工具,用来导入mysqldump加-T参数后导出的文本文件。

      sql 复制代码
      -- 语法
      	mysqlimport [options] db_name textfile1 [textfile2 ...]
      
      -- 示例
      	mysqlimport -uroot -p1234 test /tmp/city.txt
    • 如果需要导入sql文件,可以使用mysql中的source指令

      sql 复制代码
      -- 语法
      	source /root/xxxxx.sql
相关推荐
TayTay的学习笔记1 分钟前
LinkedList底层结构和源码分析(JDK1.8)
java·笔记·学习
wjpwjpwjp08311 小时前
【3D视觉学习笔记2】摄像机的标定、畸变的建模、2D/3D变换
人工智能·笔记·深度学习·学习·计算机视觉·3d
爱吃喵的鲤鱼1 小时前
MySQL——数据类型
java·数据库·mysql
cainiaojunshi2 小时前
学习笔记 ASP.NET Core Web API 8.0部署到iis
笔记·学习·asp.net
云上艺旅2 小时前
K8S学习之前站五:清理docker的overlay2 目录
学习·docker·云原生·kubernetes
charlie1145141913 小时前
计算机网络笔记再战——理解几个经典的协议HTTP章3
网络·笔记·网络协议·学习·计算机网络·http
Jozky863 小时前
自动驾驶中基于潜在世界模型学习多概率决策(LatentDriver)
人工智能·学习·自动驾驶
明月看潮生3 小时前
青少年编程与数学 02-011 MySQL数据库应用 02课题、MySQL数据库安装
数据库·mysql·青少年编程·编程与数学
用户86178277365183 小时前
整表复制
java·后端·mysql
虾球xz4 小时前
游戏引擎学习第162天
学习·游戏引擎