不一样。UPDATE比SELECT慢的根本原因在于其必须加行锁、写Undo Log和Redo Log,并强制执行两阶段提交,而SELECT默认不加锁、不写日志、不参与事务提交流程。

MySQL 的 UPDATE 语句比 SELECT 慢,不是因为“写天然就慢”,而是它多做了三件事:加行锁、写 Undo Log、写 Redo Log(且必须两阶段提交)。这些动作在高并发或大更新量下会暴露成瓶颈,而查询几乎不触发它们。
UPDATE 必须走两阶段提交,SELECT 完全不参与
MySQL 在事务中执行 UPDATE 时,InnoDB 必须保证崩溃可恢复,所以强制走 prepare → commit 两阶段提交流程。这意味着:
-
prepare阶段要把Redo Log写入磁盘(刷盘),哪怕innodb_flush_log_at_trx_commit = 1才强制刷,但 prepare 这步本身已含 fsync 开销 -
commit阶段还要再记一次Redo Log(标记事务提交),并更新binlog(如果开启)——这又是一次磁盘 I/O 或组提交等待 -
SELECT不产生任何Redo或Undo,也不进事务提交流程,自然没这部分延迟
典型现象:同一台机器上,SELECT COUNT(*) FROM t WHERE id > 1000 耗时 2ms,而 UPDATE t SET name='x' WHERE id > 1000 耗时 800ms —— 差距主要来自 Redo 刷盘和 binlog 同步开销,尤其当磁盘是机械盘或 IO 压力大时。
Buffer Pool 刷新不是“懒惰”的,UPDATE 会主动触发脏页落盘压力
很多人误以为 “Buffer Pool 是缓存,写完就完事”,其实 UPDATE 修改数据页后,该页立刻变成“脏页”,而 InnoDB 有多个机制会把它推到刷盘队列:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 当
innodb_buffer_pool_pages_dirty超过阈值(默认 75%),后台线程开始异步刷脏页 - 若
innodb_max_dirty_pages_pct设置过低(如 30%),会导致频繁刷盘,抢占 IO 资源 - 更关键的是:
UPDATE导致大量脏页堆积时,后续的SELECT可能因 Buffer Pool 空间不足,被迫触发同步刷脏页(innodb_buffer_pool_wait_free升高),连带拖慢查询
验证方式:SHOW ENGINE INNODB STATUS\G 查看 BUF BUFFER POOL AND MEMORY 小节中的 pages dirty 和 wait free 计数;若后者持续增长,说明 UPDATE 正在挤压 Buffer Pool。
行锁 + Undo Log 生成让 UPDATE 实际工作量远超表面
UPDATE 在 Server 层解析后,进入 InnoDB 引擎,真正执行时要完成三项原子操作:
- 对目标行加
X 锁(排他锁)—— 若该行正被其他事务锁定,当前UPDATE就卡在waiting for lock - 把旧值拷贝进
Undo Log(用于回滚和 MVCC)—— 每次修改都新增一条 undo record,undo 表空间可能膨胀 - 修改 Buffer Pool 中的数据页,并标记为 dirty —— 这步看似快,但若涉及二级索引更新,还需额外维护索引页,锁范围可能扩大
常见陷阱:UPDATE t SET status=1 WHERE user_id=123 看似简单,但如果 user_id 没索引,就会全表扫描+逐行加锁,锁住成百上千行,其他事务一碰就等;而 SELECT 即使没索引,也只读不锁(除非显式 SELECT ... FOR UPDATE)。
真正卡住 UPDATE 的,往往不是 SQL 本身,而是它背后那套保障 ACID 的机制——锁、日志、刷盘。优化方向很明确:减少锁冲突(加索引、缩小 WHERE 范围)、控制脏页节奏(调 innodb_max_dirty_pages_pct)、避免大事务(分批 UPDATE ... LIMIT 1000)。这些点漏掉任何一个,单条 UPDATE 都可能从毫秒级滑向秒级甚至分钟级。

















