it编程 > 数据库 > Mysql

Mysql事务和锁的一些概念和理解深入分析

67人参与 • 2026-09-29 • Mysql

事务的并发性和隔离性由存储引擎(如 innodb)和事务隔离级别共同决定。

mysql(尤其是 innodb 引擎)使用 行级锁(row-level locking)来实现事务的隔离性

  • 如果事务 a 修改了某一行,它会对该行加排他锁(x 锁)。
  • 此时事务 b 若也尝试修改或读取(取决于隔离级别)同一行,就可能被阻塞,直到事务 a 提交或回滚。

mysql 默认隔离级别是 repeatable read(可重复读),不同级别对并发的影响不同:

  • read uncommitted:几乎无阻塞(但可能读到脏数据)。
  • read committed:读操作一般不加锁(使用快照读),写操作加行锁。
  • repeatable read:innodb 使用 mvcc + next-key lock 防止幻读,可能在范围查询时加间隙锁(gap lock),增加锁竞争。
  • serializable:所有读操作都加共享锁,显著增加阻塞可能性,但依然不是“全局”锁。

两个概念

脏读

脏读是指一个事务读取了另一个尚未提交的事务所修改的数据。
如果那个未提交的事务后来回滚(rollback)了,那么当前事务读到的数据就是“不存在”的、无效的——这就是所谓的“脏数据”。

例子:

说明一下:

-- 事务a
update accounts set balance = balance - 100 where user = 'alice';  -- 余额从500变成400
-- (但还没 commit)

mysql执行逻辑:

执行这个sql后,数据在物理/内存层面已经被修改了。

innodb 会在内存中(buffer pool)修改该行的数据页,将 balance 从 500 改为 400

在正常隔离级别下,其他事务是看不到这个update后的400的

原理:

其他事务使用 read committed / repeatable read(默认)

幻读

幻读是指在一个事务中,两次执行相同的查询,但第二次查询返回了第一次没有的结果(即“凭空出现”的新行),就像出现了“幻影”。

幻读通常发生在插入(insert)操作上,而不是更新。

-- 事务a 开始
start transaction;

-- 第一次查询
select * from students where score > 80;  -- 返回 0 行

-- 此时,事务b 插入了一条新记录:
insert into students (id, name, score) values (3, 'tom', 90);
commit;

-- 事务a 再次执行相同查询
select * from students where score > 80;  -- 现在返回 tom!

幻读:新增行导致查询结果集变大(针对新插入的行)---结果集会变大。

区别:

总结就是 放心使用。。遇到其他问题再说

mysql 默认(innodb + repeatable read):

buffer pool

基本概念:

为什么需要 buffer pool?

buffer pool 的基本结构与内容

buffer pool 的工作机制 - lru算法

 buffer pool 相关配置

例子说明:

初始状态

第一次查询

执行sql: 你执行了 select * from users where id = 100;

查找过程:
磁盘读取:

返回结果: 在内存中的"page 5"里找到id=100的记录,返回给客户端。

buffer pool状态: 此刻,buffer pool里有了1个页,即"page 5"。

第二次查询(命中缓存)

执行sql: 你紧接着又执行了 select * from users where id = 100; (还是查id=100)

查找过程:

返回结果: 引擎直接在内存中的"page 5"里找到记录,快速返回给客户端。这次没有发生任何磁盘i/o! 这就是缓存带来的巨大性能提升。

访问其他数据

执行sql: select * from users where id = 200;

查找过程: buffer pool里没有包含id=200的页。

磁盘读取: 假设id=200在磁盘的"page 8"中,引擎将"page 8"读入buffer pool。

更新lru列表: 为了管理缓存,innodb会维护一个lru(最近最少使用)链表。

buffer pool状态: 现在buffer pool里有"page 5"和"page 8"两个页。

插入数据

执行sql: insert into users (username, email, password_hash) values ('new_user', 'new@example.com', '...');

查找过程: innodb需要更新聚集索引(主键索引),找到插入位置。同样,它会先在buffer pool中查找相关的索引页。

缓存命中/未命中: 如果相关的索引页(比如根节点或中间节点的页)在buffer pool中,就直接修改内存;如果没有,也需要先从磁盘读取到buffer pool。

修改内存: 将新记录插入到buffer pool中的相应页里。

标记脏页: 这个被修改过的页现在被称为“脏页”(dirty page),因为它内存中的内容与磁盘上的不一致。

后续处理: 这个脏页不会立即写回磁盘,而是等待后台线程(如innodb_io_capacity控制的刷新线程)在合适的时机将其刷回磁盘,以保证数据持久化。

buffer pool满了怎么办?(lru算法详解)

假设经过一系列操作后,buffer pool的10个页都已经被占满,分别是 page 1, 2, 3, ..., 10。并且,根据它们的访问频率,它们在lru链表中的顺序是这样的(越靠近头部表示越“热”):
[page 7, page 3, page 9, page 2, page 8, page 5, page 1, page 4, page 6, page 10](page 7 是最近最常访问的,page 10 是最近最少访问的)

执行sql: select * from users where id = 300; 假设这条记录在 page 15 中。

buffer pool已满: buffer pool已经没有空闲空间了。

触发淘汰: innodb需要从buffer pool中“踢”出一个页,为即将读入的"page 15"腾地方。根据lru算法,它会选择lru链表尾部的页,也就是page 10。

检查脏页: innodb检查page 10是否是脏页。

加载新页: 将磁盘上的page 15读取到刚刚释放出来的内存空间中。

更新lru列表: 将page 15插入到lru链表的头部。

最终lru列表: 可能变成 [page 15, page 7, page 3, page 9, page 2, page 8, page 5, page 1, page 4, page 6] (page 10 已被移除)

总结 

到此这篇关于mysql事务和锁的一些概念和理解深入分析的文章就介绍到这了,更多相关mysql事务和锁内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

赞 (0)

您想发表意见!!点此发布评论

推荐阅读

MySQL表约束的实现

09-29

MySQL大表优化之表分区归档与冷热分离实战

09-29

MySQL面试核心知识点总结与原理详细解析

09-29

MySQL信息收集的常用方法汇总

09-29

mysql数据库中null是什么意思详解

09-29

MySQL缓存策略与读写分离的深入剖析

09-29

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论