mysql数据库的事务隔离级别与并发控制

来源:这里教程网 时间:2026-02-28 20:46:13 作者:

MySQL 默认隔离级别是
REPEATABLE READ
,但它的实现方式和标准 SQL 不同

MySQL 的

REPEATABLE READ
并不靠锁住所有读取范围来防止幻读,而是用
Next-Key Locks
(间隙锁 + 行锁)配合 MVCC 实现。这意味着:同一事务中多次
SELECT
看到的数据快照一致,但新插入的行是否可见,取决于是否落在被锁定的间隙里。

常见误解是认为

REPEATABLE READ
能完全避免幻读 —— 实际上,在「当前读」(如
SELECT ... FOR UPDATE
UPDATE
DELETE
)下,间隙锁会阻止其他事务在范围内插入,从而抑制幻读;但在纯「快照读」下,新插入的行对本事务不可见,不是因为被锁住,而是因为 MVCC 只看到事务开始时已存在的版本。

READ COMMITTED
下每次快照读都重新获取最新已提交版本,所以不会复现「不可重复读」,但幻读更易发生(间隙锁在 RC 下只在唯一索引等少数场景启用)
READ UNCOMMITTED
几乎不用:可能读到未提交数据,
SELECT
不加任何锁,性能高但业务逻辑极难保证
SERIALIZABLE
会让普通
SELECT
隐式加上
LOCK IN SHARE MODE
,所有读写都串行化,开销大,通常只在极严苛审计场景才考虑

如何查看和修改当前会话或全局隔离级别

隔离级别是会话级变量,可动态调整,但需注意生效时机:修改后只影响后续语句,不影响已开启的事务。

SELECT @@transaction_isolation;
-- 返回类似:REPEATABLE-READ(MySQL 8.0+)或 TRANSACTION_ISOLATION(5.7)
<p>SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 需 SUPER 权限,且新连接才生效

注意:

GLOBAL
设置不会改变当前已连接会话的级别;如果应用使用连接池,改全局后旧连接仍维持原级别,必须重启连接或显式
SET SESSION

MySQL 5.7 使用
@@tx_isolation
,8.0+ 统一为
@@transaction_isolation
配置文件中设置:在
my.cnf
transaction-isolation = READ-COMMITTED
(注意用短横线)
JDBC 连接串可指定:添加
&sessionVariables=transaction_isolation='READ-COMMITTED'

SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE
的锁行为差异

这两者都触发「当前读」,绕过 MVCC 快照,直接访问最新行并加锁,但锁类型和兼容性不同:

FOR UPDATE
在符合条件的记录上加
X 锁
(排他锁),同时在扫描范围加
Gap Lock
(除非唯一索引等效精确匹配)
LOCK IN SHARE MODE
S 锁
(共享锁),允许其他事务加 S 锁,但阻塞 X 锁;同样有间隙锁行为
若查询条件无索引,MySQL 会退化为全表扫描 + 全表记录加锁,极易引发锁等待甚至死锁

典型陷阱:执行

SELECT * FROM t WHERE name = 'foo' FOR UPDATE
,但
name
列没有索引 → 锁住整张表所有行,而不是仅匹配行。

幻读到底在什么情况下会发生?

幻读指「同一事务中,前后两次相同范围的

SELECT
返回行数不同」。它是否发生,取决于读类型和隔离级别:

REPEATABLE READ
下,快照读(普通
SELECT
)不会出现幻读 —— 因为始终读取事务启动时的快照
但在当前读下(如
SELECT ... FOR UPDATE
),如果另一个事务在间隙中插入了新行,本事务再次执行相同当前读时会看到新行 → 这就是幻读
READ COMMITTED
下,快照读每次取最新已提交版本,所以不仅可能幻读,还可能出现不可重复读

真正难处理的是「基于幻读做业务判断」的场景,比如先查有没有订单,再插入 —— 即使用了

SELECT ... FOR UPDATE
,若没锁住对应间隙,仍可能被并发插入打破逻辑。这时候得靠唯一约束或应用层分布式锁兜底。

相关推荐