一个不走索引的更新语句,到底会不会锁全表

博客探讨了在不同隔离级别下,MySQL中不使用索引的更新语句是否会锁全表的问题。在RR隔离级别下,会锁全表;而在RC隔离级别下,只会锁住匹配条件的行。通过实例分析,揭示了RC级别下更新语句的加锁过程,解释了为何特定删除操作会被堵塞。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

经常看到有人说,如果一个DML不能走索引,就要锁全表。这正确吗?
我曾经也这样以为,直到现在我才发现,对一半错一半吧。
答案应该是:
例如如下一个表:

mysql> show create table sam\G
*************************** 1. row ***************************
       Table: sam
Create Table: CREATE TABLE `sam` (
  `id` int(11) NOT NULL,
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_c2` (`col2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> select * from sam;
+----+------+------+
| id | col1 | col2 |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    2 |
|  3 |    3 |    3 |
+----+------+------+
3 rows in set (0.00 sec)

当执行update sam set col1=4 where col1=3
1.如果隔离级别是RR,那么确实会锁全表,是覆盖全表的next-key锁;
2.如果隔离级别是RC,那么只会锁住col1=3这一行,是行锁。

那么我们来验证一下吧!

一、RR隔离级别下
会话1执行update sam set col1=4 where col1=3

mysql> show variables like '%isolation%';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation          | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update sam set col1=4 where col1=3;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

会话2执行任何加锁操作,都被堵塞

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from sam where col1=1;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> delete from sam where id=1;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> select * from sam where id=1 lock in share mode;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted

所以,会话1是施加了全表的X锁

二、RC隔离级别下
会话1执行update sam set col1=4 where col1=3

mysql> show variables like '%isolation%';
+-----------------------+----------------+
| Variable_name         | Value          |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
| tx_isolation          | READ-COMMITTED |
+-----------------------+----------------+
2 rows in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update sam set col1=4 where col1=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

会话2执行delete from sam where col1=1,被堵塞

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from sam where col1=1;

这时候是不是觉得很奇怪?不是说RC下加的是行锁吗?耍猴呢?
别急,继续看
会话2继续执行delete from sam where id=1

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from sam where col1=1;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> delete from sam where id=1;
Query OK, 1 row affected (0.00 sec)

这时就执行成功了,如果会话1加的是表锁,就不可能成功的。

三、什么原理?
RC下执行update sam set col1=4 where col1=3,加锁过程其实是这样的:
1.确实需要全表扫描sam表,在扫描过程中每一行都加上X锁;
2.当读到了col1=3的行,就执行更新;
3.当整个sam表都读完了,col1=3的行也就全部更新完了,这时就会把col1<>3的行锁释放;
4.所以,delete from sam where id=1才会执行成功,因为id=1可以直接定位到那一行数据,而这一行数据已经没有了会话1加的X锁了。

那么问题来了,为什么会话2执行delete from sam where col1=1不行呢,不也是id=1那一行吗?
这里由于where条件是col1=1,需要走全表扫描才能确定哪些行col1=1,而不像id=1那样,可以唯一确定是哪一行。
在全表扫描寻找col1=1的过程中,同样是需要给每一行都上X锁,所以id=1,id=2这两行,会话2其实是加X锁成功了,但当读到id=3时,由于这一行被会话1上了X锁且未提交事务,所以会话2就没办法上锁了,就只能等待。
所以会话2等待的是col1=3上的行锁,而不是表锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值