MySQL基本概念


第一部分:存储引擎与索引结构

1.1 什么是存储引擎?

MySQL 支持多种存储引擎 (Storage Engine),负责数据的存储、检索和事务管理。

最常用的是 InnoDB(默认,支持事务、行锁、外键)和 MyISAM(不支持事务,表锁)。

生产环境几乎全部使用 InnoDB


1.2 InnoDB 的数据组织:B+ 树

🔍 为什么用 B+ 树?
  • 磁盘 I/O 是性能瓶颈 → 需要减少磁盘读取次数
  • B+ 树是多路平衡搜索树,高度低(通常 3~4 层),一次查询只需 3~4 次磁盘 I/O
🌲 B+ 树 vs B 树
特性 B 树 B+ 树(InnoDB 使用)
数据存储位置 内部节点 + 叶子节点 仅叶子节点存数据
叶子节点连接 双向链表连接(利于范围查询)
查询效率 稳定 更稳定 + 范围扫描快
📂 InnoDB 的两类 B+ 树索引
类型 说明 特点
聚簇索引(Clustered Index) 主键索引,叶子节点存整行数据 表数据按主键物理排序
二级索引(Secondary Index) 普通索引,叶子节点存主键值 查非主键字段需"回表"

💡 例:SELECT name FROM users WHERE id = 100;

  • id 是主键 → 走聚簇索引,1 次 I/O
  • 若查 email = 'a@b.com',而 email 有二级索引 → 先查二级索引得 id,再回表查聚簇索引 → 2 次 I/O

第二部分:事务与 ACID

2.1 什么是事务(Transaction)?

事务是一组数据库操作,要么全部成功,要么全部失败,是一个不可分割的工作单元。

sql 复制代码
BEGIN;
UPDATE account SET balance = balance - 100 WHERE user = 'A';
UPDATE account SET balance = balance + 100 WHERE user = 'B';
COMMIT; -- 或 ROLLBACK

2.2 ACID 四大特性

特性 含义 MySQL 如何实现
Atomicity(原子性) 事务不可分割 Undo Log(回滚日志)
Consistency(一致性) 事务前后数据合法(如余额 ≥0) 应用逻辑 + 约束(唯一索引等)
Isolation(隔离性) 并发事务互不干扰 MVCC + 锁机制
Durability(持久性) 提交后数据永久保存 Redo Log + innodb_flush_log_at_trx_commit=1

2.3 隔离级别与并发问题

隔离级别 脏读 不可重复读 幻读 MySQL 默认
Read Uncommitted
Read Committed (Oracle 默认)
Repeatable Read ⚠️(基本解决) InnoDB 默认
Serializable 性能差

🔍 InnoDB 在 RR 级别通过 MVCC + Next-Key Lock 解决幻读

三大并发问题:
  • 脏读:读到未提交的数据
  • 不可重复读:同一事务中多次读,结果不同(行被 update)
  • 幻读:同一事务中多次查,行数不同(有新行 insert)

第三部分:日志系统 ------ 崩溃恢复的核心

InnoDB 通过 WAL(Write-Ahead Logging) 机制保证性能与安全:

3.1 Redo Log(重做日志)

  • 作用 :崩溃恢复时重放已提交事务
  • 特点
    • 物理日志(记录"页修改")
    • 循环写入(固定大小,如 4GB)
    • 先写 Redo Log,再改内存数据页

💡 innodb_flush_log_at_trx_commit = 1 控制 Redo Log 刷盘时机

3.2 Undo Log(回滚日志)

  • 作用
    • 事务回滚
    • MVCC(多版本并发控制)提供历史快照
  • 存储:在共享表空间或独立 undo 表空间

3.3 Binlog(二进制日志)

  • 作用
    • 主从复制
    • 基于时间点的数据恢复
  • 格式
    • STATEMENT:记录 SQL(可能不一致)
    • ROW:记录每行变更(推荐
    • MIXED:混合

💡 sync_binlog = 1 控制 Binlog 刷盘时机


第四部分:主从复制原理

4.1 复制流程(异步)

Slave_DB Slave_SQL_Thread Relay_Log Slave_IO_Thread Master Slave_DB Slave_SQL_Thread Relay_Log Slave_IO_Thread Master 1. 写 Binlog 2. Slave 连接,请求 Binlog 3. 写 Relay Log 4. 重放 Relay Log

4.2 GTID(全局事务 ID)

  • 格式:server_uuid:transaction_id
  • 优势:自动定位复制位点,切换主从无需找 binlog_file + position

第五部分:高可用架构(无 MGR/MHA)

5.1 架构选择:双主(Active-Standby) + 多从

  • 只允许一个主写,避免冲突
  • 备主平时 read_only = ON
  • auto_increment 奇偶分离防主键冲突

5.2 关键配置(所有节点)

ini 复制代码
# 安全底线
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1

# 强一致性基础
gtid_mode = ON
enforce_gtid_consistency = ON
binlog_format = ROW

# 防冲突
auto_increment_increment = 2
auto_increment_offset = 1  # 主库=1,备库=2

5.3 手动故障切换步骤

  1. 停止备库复制:STOP SLAVE; RESET SLAVE ALL;
  2. 解除只读:SET GLOBAL read_only = OFF;
  3. 漂移 VIP 或更新应用配置
  4. 修复原主后,作为新从库加入

第六部分:Redis 补充(缓存与加速)

6.1 Redis 核心价值

  • 缓存:减轻 MySQL 读压力
  • 原子操作INCR/DECR 实现库存扣减
  • 数据结构:List(队列)、ZSet(排行榜)

6.2 秒杀场景示例

python 复制代码
stock_key = "stock:1001"
new_stock = r.decr(stock_key)
if new_stock >= 0:
    # 创建订单
else:
    r.incr(stock_key)  # 补偿!
    raise HTTPException(429, "售罄")

INCR/DECR 是 Redis 原子命令,线程安全


第七部分:总结 ------ 生产 Checklist

类别 必做项
存储 使用 InnoDB,主键自增
索引 合理设计二级索引,避免回表
事务 用 RR 隔离级别,短事务
日志 双1配置 + ROW 格式 binlog
复制 GTID + 半同步(可选)
高可用 双主单写 + VIP + 监控
缓存 Redis 缓存热点数据,防穿透/击穿

🌟 记住
数据库不是黑盒。理解 B+ 树、事务、日志、复制原理,才能在高并发、高可用场景下做出正确决策。

非常好的问题!MVCC(Multi-Version Concurrency Control,多版本并发控制) 是现代数据库(如 MySQL InnoDB、PostgreSQL、Oracle)实现高并发读写 的核心机制。它让你在不加锁 的情况下,也能安全地进行快照读(Snapshot Read),极大提升系统吞吐量。

下面我们用 通俗语言 + 技术细节 + 示例 来彻底讲清楚 MVCC。


一、为什么需要 MVCC?

🚫 传统锁机制的问题

假设没有 MVCC,要保证事务隔离性,只能靠加锁

  • 读操作 → 加共享锁(S锁)
  • 写操作 → 加排他锁(X锁)

后果

  • 一个长事务在更新某行,其他所有读请求都会被阻塞
  • 系统并发能力极低,用户体验差。

✅ MVCC 的目标

读不阻塞写,写不阻塞读!

即使有事务在修改数据,其他事务仍能看到一致的历史版本,无需等待。


二、MVCC 的核心思想

"不是直接修改数据,而是保留多个版本,每个事务看到自己该看的版本。"

就像 Git 的分支:

  • 每次修改生成一个新 commit(新版本)
  • 不同人可以基于不同 commit 工作,互不影响

三、InnoDB 如何实现 MVCC?

InnoDB 通过以下 3 个关键技术 实现 MVCC:

1️⃣ 隐藏字段(每行记录自带)

InnoDB 在每行数据中隐式添加 3 个字段(用户不可见):

字段 说明
DB_TRX_ID 最后修改该行的事务 ID
DB_ROLL_PTR 回滚指针,指向 Undo Log 中的旧版本
DB_ROW_ID 行 ID(仅当表无主键时使用)

💡 事务 ID(trx_id)是全局递增的数字,启动时分配。


2️⃣ Undo Log(版本链)

  • 每次 UPDATE/DELETE 时,不直接覆盖原数据 ,而是:
    1. 将旧行复制到 Undo Log
    2. 新行的 DB_ROLL_PTR 指向这个旧版本
  • 多次修改形成 版本链(Version Chain)
text 复制代码
当前行 (trx_id=105)
   ↑
   | DB_ROLL_PTR
   ↓
旧版本 (trx_id=102)
   ↑
   | DB_ROLL_PTR
   ↓
更旧版本 (trx_id=100)

3️⃣ Read View(读视图)

当一个事务执行快照读 (如普通 SELECT)时,InnoDB 会创建一个 Read View ,决定"能看到哪些版本"。

Read View 包含 4 个关键信息:
成员 说明
m_ids 当前活跃事务 ID 列表(未提交的事务)
min_trx_id m_ids 中最小的事务 ID
max_trx_id 创建 Read View 时,下一个将分配的 trx_id
creator_trx_id 当前事务自己的 ID(若为读事务则为 0)

四、可见性判断规则(核心!)

给定一行数据(版本),当前事务能否看到它?按顺序判断:

  1. 如果行的 DB_TRX_ID == 当前事务 ID

    → 自己改的,可见

  2. 如果行的 DB_TRX_ID < min_trx_id

    → 该行在 Read View 创建前已提交,可见

  3. 如果行的 DB_TRX_IDmax_trx_id

    → 该行在 Read View 创建后才开启,不可见

  4. 如果 DB_TRX_ID[min_trx_id, max_trx_id) 区间内

    → 检查是否在 m_ids(活跃事务列表)中:

    • → 未提交,不可见
    • 不在 → 已提交,可见
  5. 如果不可见,就沿着 DB_ROLL_PTR 找上一个版本,重复判断

✅ 这就是"一致性非锁定读"的原理!


五、举个完整例子 🌰

场景:账户余额查询

时间 事务 A(trx_id=100) 事务 B(trx_id=101)
T1 BEGIN;
T2 SELECT balance FROM account WHERE id=1;看到 1000
T3 BEGIN;
T4 UPDATE account SET balance = 800 WHERE id=1;
T5 SELECT balance FROM account WHERE id=1;仍看到 1000!
T6 COMMIT;
T7 SELECT balance FROM account WHERE id=1;看到 800

分析:

  • T2 时,事务 A 创建 Read View:m_ids=[100], min=100, max=102
  • T5 时,事务 A 再次 SELECT:
    • 当前行 DB_TRX_ID=101(事务 B 修改的)
    • 101[100, 102) 且在 m_ids?→ 事务 B 未提交(T5 时还在运行) → 不可见
    • 沿着 Undo Log 找到旧版本:DB_TRX_ID=100 → 是自己事务 → 可见!值为 1000

✅ 事务 A 始终看到事务开始时的一致快照,不受其他未提交事务影响。


六、MVCC 与隔离级别的关系

隔离级别 是否使用 MVCC? 行为
Read Committed (RC) 每次 SELECT 都创建新 Read View → 能看到其他事务已提交的新数据
Repeatable Read (RR) 事务中第一次 SELECT 创建 Read View,后续复用 → 保证可重复读
Serializable 强制加锁,退化为串行执行

🔍 MySQL InnoDB 默认 RR 级别,正是靠 MVCC 实现"幻读基本解决"


七、MVCC 的优势与代价

✅ 优势

  • 高并发:读写不互相阻塞
  • 一致性:提供事务开始时的数据快照
  • 性能好:避免大量读锁开销

⚠️ 代价

  • 存储开销:Undo Log 占用额外空间
  • 清理延迟:旧版本不能立即删除(需等所有可能用到它的事务结束)
  • 长事务危害:会导致 Undo Log 无法 purge,磁盘爆满!
相关推荐
风哥2号14 分钟前
数据库教程FGMT29‑Linux平台MySQL5.7安装配置与管理入门
数据库
杨云龙UP1 小时前
MySQL Host is blocked because of many connection errors 导致 JDBC 连接失败排查与解决
linux·运维·网络·数据库·sql·mysql·登录失败
这个DBA有点耶2 小时前
数据库数据同步解决方案怎么选?6款主流工具横向对比与信创选型指南
数据库·架构·dba
刃神太酷啦2 小时前
Redis 核心进阶:哨兵、集群、缓存问题与分布式锁详解----《Hello Redis!》(6)
linux·c语言·数据库·c++·redis·分布式·缓存
小雷信息医学2 小时前
不会写复杂代码也能发 SCI?手把手教你用 InSpireR 交互系统一键提取、合并与导出临床科研宽表【第三章】
数据库
程序员梅雨3 小时前
MySQL底层探索(一):什么是脏页?什么是冷热分离?
mysql·面试
旺仔不是程序员3 小时前
复合索引最左前缀原则:PostgreSQL 的 WHERE 为什么必须命中第一列
数据库·后端·sql
旺仔不是程序员3 小时前
字段类型不一致:PostgreSQL 报错与索引失效的第一元凶
数据库·后端·sql
白远山3 小时前
货运跑腿搬家平台开发实战:从需求分析到落地部署指南
数据库·数据挖掘·需求分析
geovindu3 小时前
sql: Data Modeling Patterns
数据库·sql·设计模式·sqlserver