MVCC 多版本并发控制 + 锁机制:事务隔离级别的底层实现(三)

数据库事务系列 · 第 3 篇

引言:一个「诡异」的现象

在前两篇文章中,我们做了一个完整的演示:

  1. 第 1 篇:我们学会了事务的基本操作(BEGIN、COMMIT、ROLLBACK、SAVEPOINT),并验证了原子性和持久性。
  2. 第 2 篇:我们把隔离级别调来调去,亲眼看到了脏读、不可重复读、幻读,以及四种隔离级别如何应对它们。

但你有没有想过一个问题------

演示三(Repeatable Read) 中,我们看到了一个「诡异」的现象:

时间 终端B(柜员小李) 终端A(柜员小张) 张三余额
T1 BEGIN; 100
T2 SELECT balance FROM account WHERE id = 1; 读到 100
T3 BEGIN;
T4 UPDATE account SET balance = 0 WHERE id = 1;
T5 COMMIT; 已提交,物理数据变为 0
T6 SELECT balance FROM account WHERE id = 1; 仍然读到 100

诡异的地方在于:

终端A 已经在 T5 时刻提交了事务,数据库里的「真实数据」已经变成了 0。但终端B 在 T6 时刻(自己的事务还没结束)读取到的,依然是 100。

终端B 凭什么能看到「已经不存在」的旧数据?

这个问题,指向了 MySQL 事务隔离性最核心的底层机制------

MVCC(Multi-Version Concurrency Control,多版本并发控制)

一、MVCC 的核心思想:每个事务都有自己的「专属快照」

1.1 什么是 MVCC?

MVCC 的全称是 Multi-Version Concurrency Control,翻译过来就是「多版本并发控制」。

它的核心思想非常巧妙:

每次修改数据时,不直接覆盖旧数据,而是生成一个新版本。旧版本继续保留,供其他事务读取。

用生活场景来理解:

你在 Git 上提交代码,每次提交都会生成一个新的 commit。团队成员可以继续查看旧的 commit,也可以拉取最新的 commit。大家各看各的版本,互不干扰。

MVCC 在数据库里干的是同样的事情:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    数据行的版本链                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  版本 V1 (balance=100)  ←── 事务B 正在读这个版本               │
│        │                                                        │
│        ▼ (事务A 修改并提交)                                      │
│  版本 V2 (balance=0)    ←── 物理数据现在是这个版本              │
│                                                                 │
│  事务B 读到的还是 V1------因为事务B 启动时,V2 还没诞生             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

1.2 MVCC 解决了什么问题?

MVCC 让数据库实现了两个关键能力:

能力 说明 对应业务价值
读写不互斥 读操作不会阻塞写操作,写操作也不会阻塞读操作 高并发场景下,查询和更新可以同时进行
一致性读 每个事务都能读到「自己启动那一刻」的数据快照 事务内多次查询结果一致(可重复读)

没有 MVCC 之前,读操作需要加锁,写操作也要加锁------读会阻塞写,写也会阻塞读,并发性能很差。有了 MVCC,读操作直接读取「旧版本」,不需要等写操作完成。

1.3 MVCC 的适用边界

MVCC 解决了「读写冲突 」,但解决不了「写写冲突」。

如果两个事务同时修改同一行数据(比如终端A 和终端C 同时要把张三的余额改为 0 和 50),MVCC 就无能为力了------因为最终只能有一个版本是「最新」的。

这个冲突,由锁机制来处理。MVCC 和锁,一个管「读-写」,一个管「写-写」,共同构建了完整的并发控制体系。

完整的分工图:

二、MVCC 的三大核心组成部分

在深入细节之前,我们先看一张完整的 MVCC 工作流程图

这张图把隐藏字段、undo log 版本链、read view 三者的关系一次性展现出来。接下来的三小节,我们会逐一拆解图中的每一个部件。

2.1 隐藏字段:每行数据都有的「身份证」

在 InnoDB 中,每一行数据除了我们定义的字段(id、name、balance),还有三个隐藏字段

隐藏字段 名称 大小 存储内容
DB_TRX_ID 事务ID 6 字节 最近一次修改该行的事务ID
DB_ROLL_PTR 回滚指针 7 字节 指向 Undo Log 中旧版本的指针
DB_ROW_ID 行ID 6 字节 单调递增的行ID(无主键时用于生成聚集索引)

我们通常不用关注 DB_ROW_ID,但 DB_TRX_ID 和 DB_ROLL_PTR 是理解 MVCC 的关键

用「张三的余额」来看:

trx_id 是自增的,意味着什么?

规则 含义
trx_id 越小 该版本被创建得越早
trx_id 越大 该版本被创建得越晚
当前事务的 trx_id 就是当前事务在系统中的唯一编号

这就是 read view 判断「谁先谁后」的依据------用 trx_id 的大小关系,判断数据版本和当前事务的时间先后。

2.2 Undo Log 链:数据版本的「全家桶」

每次修改数据时,InnoDB 会把修改前的旧版本 记录到 Undo Log 中。多个旧版本通过 roll_pointer 串联成一条版本链

关键理解:多个事务并行操作同一行时,每个版本都记录着「谁改的」,并通过 roll_pointer 串联成一个链表。

每个版本都记录了两个关键信息:

  • 是谁(哪个事务ID)创建了这个版本
  • 上一个版本在哪里(DB_ROLL_PTR)
作用 说明
支持回滚 事务回滚时,沿着版本链找到旧版本,恢复数据
支持 MVCC read view 沿着版本链遍历,找到「可见」的版本
支持一致性读 RR 级别下,始终读取事务开始时的版本快照

2.3 Read View:事务启动时的「快照相机」

Read View 是 MVCC 中最重要的概念。它的作用是:

在事务执行 SELECT 时,生成一个「快照」,记录当时系统中所有活跃事务的状态。

Read View 包含三个核心字段:

字段 含义
min_trx_id 当前所有活跃事务中,最小的 ID
max_trx_id 系统下一个要分配的事务 ID(边界值)
m_ids 当前所有活跃事务的 ID 列表(即正在进行中、还没提交的事务)

Read View 生成后,数据可见性的判断规则:

当要读取某行数据时,看该行的 DB_TRX_ID(创建该版本的事务ID):

三、RR 与 RC 的核心差异:Read View 生成时机不同

这是理解「为什么 RR 能可重复读,而 RC 不能」的关键。

3.1 Repeatable Read:一个事务只用一张「快照」

在 RR 级别下:

Read View 在事务中第一次执行 SELECT 时生成,之后一直复用,直到事务结束。

这就是终端B 在演示三中「始终读到 100」的原因:

时间 终端B Read View 状态
T1 BEGIN; 事务开始,但还没生成 Read View
T2 SELECT ...(读到 100) 生成 Read View,记录了当时的数据快照
T3 终端A 修改并提交(100 → 0) 事务已提交,但 Read View 不变
T4 SELECT ...(仍然读到 100) 复用同一个 Read View,仍然看到旧快照
复制代码
┌─────────────────────────────────────────────────────────────────┐
│              RR 级别:Read View 复用示意图                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  事务B 时间线                                                   │
│  ────────────────────────────────────────────────►              │
│  BEGIN    第一次 SELECT    第二次 SELECT       COMMIT          │
│     │           │               │               │              │
│     │      ┌────▼────┐     ┌────▼────┐         │              │
│     │      │生成     │     │复用     │         │              │
│     │      │Read View│     │Read View│         │              │
│     │      └─────────┘     └─────────┘         │              │
│     │           │               │               │              │
│  数据快照:    100             100             100 → 事务结束  │
│                                                                 │
│  即使终端A 在中间提交了修改(0),终端B 看到的始终是 100        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.2 Read Committed:每次 SELECT 都生成一张新「快照」

在 RC 级别下:

每次执行 SELECT,都重新生成一个 Read View。

这就是为什么 RC 会出现「不可重复读」------每次查询都看到最新的已提交数据。

时间 终端B Read View 状态
T1 BEGIN; 事务开始
T2 SELECT ...(读到 100) 生成 Read View #1
T3 终端A 修改并提交(100 → 0) 事务已提交
T4 SELECT ...(读到 0) 重新生成 Read View #2,看到了最新的已提交数据

四、锁机制:处理「写-写冲突」的唯一武器

MVCC 解决了「读-写冲突」,但「写-写冲突」必须由锁机制来处理。

4.1 为什么需要锁?

想象这个场景:

柜员小张(终端A)正在修改张三的余额:100 → 0(事务未提交)

柜员小王(终端C)也在修改张三的余额:100 → 50(事务未提交)

如果两个事务同时修改同一行数据,最终结果应该是 0 还是 50?

MVCC 解决不了这个问题,因为最终只能有一个版本成为「最新版本」。所以,第二个修改的事务必须等待第一个修改的事务完成(提交或回滚)

这就是锁的作用------让「写-写」操作排队执行

4.2 锁的分类(按粒度)

锁的「粒度」是指锁定的范围大小。粒度越大,锁定的数据越多,并发性能越差;粒度越小,锁定的数据越少,并发性能越好。

4.2.1 表级锁(Table Lock)

锁定整张表,粒度最大,并发最差。

场景: 在执行 ALTER TABLEDROP TABLE 等 DDL 操作时,会加表级锁。

类比:把整个仓库锁上,所有人都不能进出。

4.2.2 行级锁(Row Lock)

锁定某一行记录,粒度最小,并发最好。

场景: InnoDB 在执行 UPDATE ... WHERE id = 1 时,只锁定 id=1 的这一行,其他行不受影响。

类比:只锁住仓库里的某一个货架,其他人还能搬其他货架的东西。

4.2.3 页级锁(Page Lock)

锁定一个数据页(通常 16KB),介于表锁和行锁之间。

说明: MySQL InnoDB 主要使用行锁,页级锁在一些其他数据库中更常见(如 SQL Server)。

4.3 锁的分类(按模式)

锁模式 英文 俗称 作用
共享锁 Shared Lock 读锁(S锁) 允许其他事务读取,不允许修改
排他锁 Exclusive Lock 写锁(X锁) 不允许其他事务读取或修改
4.3.1 共享锁(S锁,读锁)

多个事务可以同时持有共享锁,但不能修改数据。

  • 加了 S 锁的数据,其他事务可以 ,但不能
  • 其他事务也可以加 S 锁,但不能加 X 锁。

加锁方式:

sql 复制代码
SELECT * FROM account WHERE id = 1 LOCK IN SHARE MODE;

业务场景: 读取数据用于生成报表。报表生成过程中,数据可以被其他人读,但不希望被修改(防止报表数据不一致)。

4.3.2 排他锁(X锁,写锁)

只允许一个事务持有排他锁,其他事务既不能读也不能写。

  • 加了 X 锁的数据,其他事务不能读 (会被阻塞),也不能写
  • 在事务提交或回滚之前,X 锁不会释放。

加锁方式:

sql 复制代码
-- 自动加 X 锁(任何修改操作都会自动加 X 锁)
UPDATE account SET balance = 0 WHERE id = 1;

-- 手动加 X 锁(SELECT ... FOR UPDATE)
SELECT * FROM account WHERE id = 1 FOR UPDATE;

业务场景: 查询某条数据并准备修改。在查询和修改之间,不希望其他事务修改这条数据。

4.4 共享锁 vs 排他锁 兼容性矩阵

锁类型 共享锁(S) 排他锁(X)
共享锁(S) ✅ 兼容 ❌ 冲突
排他锁(X) ❌ 冲突 ❌ 冲突

一句话记住:

  • 多个读可以同时进行(S 和 S 兼容)
  • 只要有写,其他人都不能动(X 和谁都冲突)

4.5 意向锁(Intention Lock):表级锁和行级锁的「协调员」

意向锁是 InnoDB 中一个非常重要但容易被忽视的概念。它的作用非常纯粹:

意向锁是一种「表级锁」,用于表示一个事务「打算」在表中的某些行加锁。

为什么需要意向锁?

想象一个场景:

  • 事务A 正在修改 account 表中的某一行(加行级 X 锁)
  • 事务B 想要锁定整张 account 表(加表级锁)

如果没有意向锁,事务B 需要扫描所有行,检查是否有行级锁存在------这效率极低。

有了意向锁,事务A 在加行锁之前,会先给表加一个「意向锁」作为标记。事务B 看到表上有意向锁,就知道「表里已经有行锁了」,直接等待即可。

意向锁的两种类型:

意向锁 简称 含义
意向共享锁 IS 锁 事务打算给某些行加共享锁(S 锁)
意向排他锁 IX 锁 事务打算给某些行加排他锁(X 锁)

加锁顺序(重点):

复制代码
事务要加行级 X 锁
        │
        ▼
    先加表级 IX 锁(意向排他锁)
        │
        ▼
    再加行级 X 锁(排他锁)

意向锁的兼容性:

锁类型 共享锁(S) 排他锁(X) 意向共享(IS) 意向排他(IX)
共享锁(S) ✅ 兼容 ❌ 冲突 ✅ 兼容 ❌ 冲突
排他锁(X) ❌ 冲突 ❌ 冲突 ❌ 冲突 ❌ 冲突
意向共享(IS) ✅ 兼容 ❌ 冲突 ✅ 兼容 ✅ 兼容
意向排他(IX) ❌ 冲突 ❌ 冲突 ✅ 兼容 ✅ 兼容

关键点:意向锁之间是互相兼容的。 多个事务可以同时持有 IX 锁(表示多个事务都在修改不同行),但一旦有事务要加表级 X 锁,就必须等待所有意向锁释放。

4.6 行锁的三种具体类型(InnoDB 特有)

InnoDB 的行锁不是一种,而是三种,分别应对不同的场景:

4.6.1 记录锁(Record Lock)

锁定单个索引记录。

sql 复制代码
-- 锁定 id=1 这一行
SELECT * FROM account WHERE id = 1 FOR UPDATE;

只锁住 id=1 这一条记录,其他 id 的记录不受影响。

业务场景: 柜员查询张三的余额并准备修改,防止其他事务在查询和修改之间改变数据。

4.6.2 间隙锁(Gap Lock)

锁定索引记录之间的「间隙」,防止其他事务在该间隙插入数据。

sql 复制代码
-- 假设 account 表中有 id: 1, 5, 10
SELECT * FROM account WHERE id BETWEEN 1 AND 10 FOR UPDATE;

间隙锁会锁定 (1, 5) 和 (5, 10) 之间的空隙,阻止其他事务插入 id=3 或 id=7 的新记录。

间隙锁的主要目的:防止幻读------阻止其他事务在查询范围内插入新数据。

业务场景: 柜员小李统计「余额大于 0 的账户总数」,不希望统计过程中有新账户开户(插入),否则统计数据就不准了。

4.6.3 Next-Key Lock

记录锁 + 间隙锁的组合。

复制代码
Next-Key Lock = Record Lock + Gap Lock

它既锁住了记录本身,又锁住了记录前面的间隙。

在 MySQL 的 RR(可重复读)级别下,查询使用索引时,默认加的就是 Next-Key Lock。这就是为什么 MySQL 的 RR 级别能避免大部分幻读的原因。

五、三种行锁的详细说明与案例

5.1:记录锁(Record Lock)

目标 :证明记录锁只锁住 id=1 这一行,其他行不受影响。

Step 1:终端A 锁定 id=1

mysql 复制代码
BEGIN;
SELECT * FROM account WHERE id = 1 FOR UPDATE;

Step 2:终端B 修改 id=1(被阻塞)

mysql 复制代码
BEGIN;
UPDATE account SET balance = 999 WHERE id = 1;

Step 3:终端B 修改 id=5(不受影响)

Ctrl+C 取消上一条被阻塞的语句,然后在终端B 执行:

mysql 复制代码
UPDATE account SET balance = 888 WHERE id = 5;

📸 截图 3 :显示 Query OK, 1 row affected------立即成功。

Step 4:终端A 提交,释放锁

mysql 复制代码
COMMIT;

此时终端B 被阻塞的 UPDATE 会立即执行成功。

结论 :记录锁只锁住 id=1 这一行,id=5 不受影响。

5.2:间隙锁(Gap Lock)

目标 :证明范围查询会锁住 (1,5) 之间的间隙,阻止在间隙内插入数据。

Step 1:重置数据

mysql 复制代码
DELETE FROM account;
INSERT INTO account VALUES (1, '张三', 100);
INSERT INTO account VALUES (5, '李四', 200);
INSERT INTO account VALUES (10, '王五', 300);

Step 2:终端A 执行范围查询,加锁

mysql 复制代码
BEGIN;
SELECT * FROM account WHERE id BETWEEN 1 AND 5 FOR UPDATE;

此时间隙锁锁住了 (1,5) 之间的空隙------也就是 id=2、3、4 的位置。**

Step 3:终端B 在间隙内插入(被阻塞)

mysql 复制代码
BEGIN;
INSERT INTO account VALUES (3, '赵六', 400);

Step 4:终端B 在间隙外插入(不受影响)

Ctrl+C 取消,然后执行:

mysql 复制代码
INSERT INTO account VALUES (7, '赵六', 400);

Step 5:终端A 提交

mysql 复制代码
COMMIT;

结论 :间隙锁锁住了 (1,5) 之间的空隙,阻止在间隙内插入数据(防止幻读)。

5.3:Next-Key Lock

目标 :证明 SELECT ... WHERE id = 5 FOR UPDATE 不仅锁住了 id=5 这一行,还锁住了它前面的间隙 (1,5)

Step 1:重置数据

mysql 复制代码
DELETE FROM account;
INSERT INTO account VALUES (1, '张三', 100);
INSERT INTO account VALUES (5, '李四', 200);
INSERT INTO account VALUES (10, '王五', 300);

Step 2:终端A 锁定 id=5

mysql 复制代码
BEGIN;
SELECT * FROM account WHERE id BETWEEN 1 AND 5 FOR UPDATE;

Step 3:终端B 修改 id=5(被阻塞------记录锁效果)

mysql 复制代码
BEGIN;
INSERT INTO account VALUES (3, '赵六', 400);

Step 4:终端B 在间隙 (1,5) 中插入(被阻塞------间隙锁效果)

Ctrl+C 取消,然后执行:

mysql 复制代码
INSERT INTO account VALUES (3, '赵六', 400);

Step 5:终端B 在间隙外插入(不受影响)

Ctrl+C 取消,然后执行:

mysql 复制代码
INSERT INTO account VALUES (6, '赵六', 400);

Step 6:终端A 提交

mysql 复制代码
COMMIT;

结论 :Next-Key Lock = 记录锁(锁住 id=5)+ 间隙锁(锁住前面的间隙 (1,5))。

三种行锁演示效果对比表
演示 终端A 的操作 终端B 的操作 结果 说明
记录锁 SELECT ... WHERE id=1 FOR UPDATE UPDATE ... WHERE id=1 ❌ 阻塞 记录锁锁住 id=1
UPDATE ... WHERE id=5 ✅ 成功 id=5 不受影响
间隙锁 SELECT ... WHERE id BETWEEN 1 AND 5 FOR UPDATE INSERT ... id=3 ❌ 阻塞 3 在间隙 (1,5) 中
INSERT ... id=7 ✅ 成功 7 不在间隙中
Next-Key Lock SELECT ... WHERE id=5 FOR UPDATE UPDATE ... WHERE id=5 ❌ 阻塞 记录锁
INSERT ... id=3 ❌ 阻塞 间隙锁(前面的间隙)
INSERT ... id=6 ✅ 成功 6 不在锁范围内

六、不同隔离级别下的锁策略对比

这是本文最重要的总结表,建议收藏:

隔离级别 使用的锁机制 特点 并发性能
Read Uncommitted 几乎不加锁(读操作不加锁,写操作加 X 锁) 性能最高,但脏读、不可重复读、幻读都可能 最高
Read Committed 读操作不加锁(MVCC),写操作加 X 锁;无间隙锁 避免脏读,但不可重复读、幻读可能 较高
Repeatable Read 读操作不加锁(MVCC),写操作加 X 锁;有间隙锁(Next-Key Lock) 避免脏读、不可重复读,基本避免幻读 中等
Serializable 读操作加 S 锁,写操作加 X 锁;所有操作串行执行 避免所有并发问题,性能最低 最低

关键差异说明:

1. Read Committed 没有间隙锁,而 Repeatable Read 有

这就是为什么 RC 会出现幻读,而 RR 不会(在大多数场景下)。

我整理了一个简要对比表:

对比维度 RC RR
是否使用 MVCC ✅ 是 ✅ 是
Read View 生成时机 每次 SELECT 第一次 SELECT
是否使用间隙锁 ❌ 否 ✅ 是
能否避免幻读 ❌ 不能 ✅ 基本能

七、MVCC + 锁 + 日志:三者如何协作

7.1 三者的职责分工

机制 负责什么 解决什么问题
Undo Log 存储数据的历史版本 原子性(回滚)+ MVCC 的数据来源
MVCC(Read View) 控制事务能看到哪个版本 隔离性中的「读-写」冲突
锁(Lock) 控制事务的并发修改 隔离性中的「写-写」冲突
Redo Log 记录修改操作 持久性(崩溃恢复)

7.2 一个完整的「查询」流程(RR 级别下)

当终端B 执行 SELECT * FROM account WHERE id = 1; 时:

7.3 一个完整的「更新」流程(所有级别下)

当终端A 执行 UPDATE account SET balance = 0 WHERE id = 1; 时:

7.4 三者协作的完整视图

八、全文总结

8.1 核心知识点回顾

知识点 核心内容 一句话记忆
MVCC 多版本并发控制,通过 Undo Log 链 + Read View 实现 每个事务都有自己的数据快照
Read View 事务启动时记录活跃事务列表,决定数据可见性 RR 级别只用一张快照,RC 每次重新拍
Undo Log 链 数据行的历史版本串联成链,供 MVCC 读取 每个版本都记录着「谁改的」和「上一个」
记录锁 锁定单行,粒度最小 只锁这一条
间隙锁 锁定索引间隙,防止插入 RR 级别用来防幻读
Next-Key Lock 记录锁 + 间隙锁 RR 级别的默认行锁
意向锁 表级锁,标记表内有行锁 让表锁和行锁能协调工作

8.2 隔离级别与底层实现对照表

隔离级别 MVCC 使用 Read View 策略 间隙锁使用 如何保证可重复读
Read Uncommitted ❌ 不用 ❌ 不用 不保证
Read Committed ✅ 使用 每次 SELECT 生成 ❌ 不用 不保证
Repeatable Read ✅ 使用 首次 SELECT 生成 ✅ 使用 固定 Read View + 间隙锁
Serializable ⚠️ 退化 无(串行化) ✅ 使用 强制串行执行

8.3 一句话全文总结

MVCC 为每个事务创建「专属快照」,让读写互不干扰;锁机制在「写-写」冲突时强制排队,保证数据最终一致;Undo Log 提供历史版本,Redo Log 保证崩溃恢复。四者共同撑起了事务隔离性的整片天空。

📌 下篇预告

前三篇文章中,我们从 ACID 聊到了 MVCC,从脏读聊到了间隙锁------理论已经讲得够多了。

但实际开发中,我们该如何选择隔离级别?死锁是怎么产生的?怎么排查?用 Prisma ORM 写事务时有哪些坑?

下一篇文章(第 4 篇) ,我们将走进生产环境,聊聊事务的实战经验

  • 不同业务场景如何选隔离级别
  • 死锁的成因、排查与预防
  • 大事务的危害与拆分策略
  • Prisma ORM 中的事务操作实践

| 无 | ❌ 不用 | 不保证 |

| Read Committed | ✅ 使用 | 每次 SELECT 生成 | ❌ 不用 | 不保证 |

| Repeatable Read | ✅ 使用 | 首次 SELECT 生成 | ✅ 使用 | 固定 Read View + 间隙锁 |

| Serializable | ⚠️ 退化 | 无(串行化) | ✅ 使用 | 强制串行执行 |

8.3 一句话全文总结

MVCC 为每个事务创建「专属快照」,让读写互不干扰;锁机制在「写-写」冲突时强制排队,保证数据最终一致;Undo Log 提供历史版本,Redo Log 保证崩溃恢复。四者共同撑起了事务隔离性的整片天空。

📌 下篇预告

前三篇文章中,我们从 ACID 聊到了 MVCC,从脏读聊到了间隙锁------理论已经讲得够多了。

但实际开发中,我们该如何选择隔离级别?死锁是怎么产生的?怎么排查?用 Prisma ORM 写事务时有哪些坑?

相关推荐
AC赳赳老秦1 小时前
CSDN 技术社区数据采集:OpenClaw 抓取公开技术热帖,生成领域技术热点周报
java·大数据·前端·数据库·python·php·openclaw
瞬间&永恒~2 小时前
【MySQL】 InnoDB 锁等待排查与并发压测实验
运维·数据库·mysql
布鲁飞丝2 小时前
对 .NET线程 异常退出引发程序崩溃的反思
数据库·c#·.net
zcmodeltech2 小时前
智能矿井沙盘模型多系统协同控制系统设计:基于STM32与Modbus RTU的感知-传输-控制一体化方案
服务器·数据库·分布式·stm32·单片机·嵌入式硬件
江晓鱼未暖3 小时前
十七、Redis 核心原理与架构详解
大数据·数据库·数据仓库·redis·缓存·架构
copyer_xyf3 小时前
PostgreSQL 做向量检索:一条单库路线
数据库·agent·nestjs
Access开发易登软件3 小时前
Access 怎么做前后端分离?用 Web API 读写 SQL Server
前端·数据库·人工智能·microsoft·excel·access
Juicedata3 小时前
腾讯云 x JuiceFS:基于 FoundationDB 的企业级统一存储实践
数据库·人工智能·科技·云计算·腾讯云
小罗水3 小时前
第13章 Redis 缓存、幂等锁与任务状态
数据库·redis·缓存
SelectDB技术团队3 小时前
当 PostgreSQL 面临性能瓶颈:80TB 电商业务迁移至 Apache Doris 的实践思考
数据库·postgresql·apache