MySQL并发访问核心是读写一致性保障,InnoDB通过MVCC+行级锁+可重复读隔离级别协同实现读不阻塞写、写不阻塞读,但写写互斥;需重点关注写冲突、锁范围、事务生命周期及乐观锁重试机制。
MySQL 并发访问不是“能不能同时连”,而是“多个连接同时读写同一份数据时,MySQL 怎么不搞错、不丢数据、不卡死”。
核心结论:InnoDB 默认用 MVCC + 行级锁 + 事务隔离级别(可重复读)协同工作,让读不阻塞写、写不阻塞读(大部分情况),但写写之间仍会互斥——这才是你实际编码时真正要盯住的边界。
它就等于:你写的代码里 SELECT 和 UPDATE 同时跑在不同线程/请求里,还可能操作同一张表、甚至同一行。
SELECT 不会等 UPDATE 提交)UPDATE user SET balance = balance - 100 WHERE id = 1 可能互相覆盖,必须靠锁或事务兜底很多人一遇到并发更新就加 SELECT ... FOR UPDATE,结果发现性能暴跌、死锁频发——因为它本质是“先查再锁”,中间有时间窗口,且锁范围容易失控。
SELECT 之前就已持有该行锁SELECT * FROM user FOR UPDATE → 整张表卡住)START TRANSACTION; -- 必须走主键或唯一索引,否则可能锁全表 SELECT balance FROM account WHERE user_id = 123 FOR UPDATE; UPDATE account SET balance = balance - 50 WHERE user_id = 123; COMMIT;
用 version 字段做乐观锁,不是加个字段就行——它只在“冲突概率低 + 更新逻辑简单”的场景下有效;一旦失败重试频繁,反而比悲观锁更耗资源。
version,漏掉就等于没锁version 比用 NOW() 更可靠UPDATE product SET stock = stock - 1, version = version + 1 WHERE id = 456 AND version = 2;
执行后若 ROW_COUNT() == 0,说明已被别人抢先更新,此时应重新查最新 stock 和 version,再试一次。
你设了 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,不代表所有 SQL 都按这个跑——InnoDB 的 MVCC 行为和锁策略,还取决于语句类型、索引、是否在事务块里。
SELECT 单独执行 → 快照读(不加锁),走 MVCCSELECT ... FOR UPDATE / LOCK IN SHARE MODE → 当前读(加锁),绕过 MVCCREAD COMMITTED 下,UPDATE 依然会对匹配行加 X 锁,且锁到事务结束REPEATABLE READ 后又用 SELECT ... FOR UPDATE,可能触发间隙锁(Gap Lock),锁住不存在的记录范围,引发隐蔽死锁真实并发问题从来不在“能不能连”,而在“谁改了什么、什么时候可见、锁住了谁、有没有漏判”。MVCC 是隐形的保护伞,锁是显性的刹车片,而事务边界,是你唯一能亲手划清的防线。