在事务中执行 SELECT 操作

本文探讨了MySQL中在线DDL操作与MDL之间的冲突问题,特别是在执行大型表的DDL操作时,如何避免对DML操作产生阻塞。文章分析了不同会话下MDL锁的获取顺序与状态,以及如何解决由未提交事务引发的阻塞。

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

前面提到 Online DDL 执行过程中需要获取 MDL,MDL (metadata lock) 是 MySQL 5.5 引入的表级锁,在访问一个表的时候会被自动加上,以保证读写的正确性。当对一个表做 DML 操作的时候,加 MDL 读锁;当做 DDL 操作时候,加 MDL 写锁。

为了在大表执行 DDL 的过程中同时保证 DML 能并发执行,前面使用了 ALGORITHM=INPLACE 的 Online DDL,但这里仍然存在死锁的风险,问题就出在 Online DDL 过程中需要 exclusive MDL 的地方。

例如,Session 1 在事务中执行 SELECT 操作,此时会获取 shared MDL。由于是在事务中执行,所以这个 shared MDL 只有在事务结束后才会被释放。

# Session 1> START TRANSACTION;> SELECT * FROM tbl_name;# 正常执行

这时 Session 2 想要执行 DML 操作也只需要获取 shared MDL,仍然可以正常执行。

# Session 2> SELECT * FROM tbl_name;# 正常执行

但如果 Session 3 想执行 DDL 操作就会阻塞,因为此时 Session 1 已经占用了 shared MDL,而 DDL 的执行需要先获取 exclusive MDL,因此无法正常执行。

# Session 3> ALTER TABLE tbl_name ADD COLUMN n INT;# 阻塞

通过 show processlist 可以看到 ALTER 操作正在等待 MDL。

+----+-----------------+------------------+------+---------+------+---------------------------------+-----------------+
| Id | User            | Host             | db   | Command | Time | State                           | Info            |│----+-----------------+------------------+------+---------+------+---------------------------------+-----------------+
| 11 | root            | 172.17.0.1:53048 | demo | Query   |    3 | Waiting for table metadata lock | alter table ... |+----+-----------------+------------------+------+---------+------+---------------------------------+-----------------+

由于 exclusive MDL 的获取优先于 shared MDL,后续尝试获取 shared MDL 的操作也将会全部阻塞

# Session 4> SELECT * FROM tbl_name;# 阻塞

到这一步,后续无论是 DML 和 DDL 都将阻塞,直到 Session 1 提交或者回滚,Session 1 占用的 shared MDL 被释放,后面的操作才能继续执行。

上面这个问题主要有两个原因:

  1. Session 1 中的事务没有及时提交,因此阻塞了 Session 3 的 DDL
  2. Session 3 Online DDL 阻塞了后续的 DML 和 DDL

对于问题 1,有些 ORM 框架默认将用户语句封装成事务执行,如果客户端程序中断退出,还没来得及提交或者回滚事务,就会出现 Session 1 中的情况。那么此时可以在 infomation_schema.innodb_trx 中找出未完成的事务对应的线程,并强制退出。

> SELECT * FROM information_schema.innodb_trx\G*************************** 1. row ***************************trx_id: 421564480355704trx_state: RUNNINGtrx_started: 2022-05-01 014:49:41trx_requested_lock_id: NULLtrx_wait_started: NULLtrx_weight: 0trx_mysql_thread_id: 9trx_query: NULLtrx_operation_state: NULLtrx_tables_in_use: 0trx_tables_locked: 0trx_lock_structs: 0trx_lock_memory_bytes: 1136trx_rows_locked: 0trx_rows_modified: 0trx_concurrency_tickets: 0trx_isolation_level: REPEATABLE READtrx_unique_checks: 1trx_foreign_key_checks: 1trx_last_foreign_key_error: NULLtrx_adaptive_hash_latched: 0trx_adaptive_hash_timeout: 0trx_is_read_only: 0trx_autocommit_non_locking: 0trx_schedule_weight: NULL1 row in set (0.0025 sec)

可以看到 Session 1 正在执行的事务对应的 trx_mysql_thread_id 为 9,然后执行 KILL 9 即可中断 Session 1 中的事务。
对于问题 2,在查询很多的情况下,会导致阻塞的 session 迅速增多,对于这种情况,可以先中断 DDL 操作,防止对服务造成过大的影响。也可以尝试在从库上修改表结构后进行主从切换或者使用 pt-osc 等第三方工具。

8 限制

  • 仅适用于 InnoDB(语法上它可以与其他存储引擎一起使用,如 MyISAM,但 MyISAM 只允许 algorithm = copy,与传统方法相同);
  • 无论使用何种锁(NONE,共享或排它),在开始和结束时都需要一个短暂的时间来锁表(排它锁);
  • 在添加 / 删除外键时,应该禁用 foreign_key_checks 以避免表复制;
  • 仍然有一些 alter 操作需要 copy 或 lock 表(老方法),有关哪些表更改需要表复制或表锁定,请查看官网;
  • 如果在表上有 ON … CASCADE 或 ON … SET NULL 约束,则在 alter table 语句中不允许 LOCK = NONE;
  • Online DDL 会被复制到从库(同主库一样,如果 LOCK = NONE,从库也不会加锁),但复制本身将被阻止,因为 alter 在从库以单线程执行,这将导致主从延迟问题。

官方参考资料:https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-limitations.html

<think>嗯,用户问的是在一个事务内重复执行单条数据的Select for update会怎么样。首先,我需要回忆一下数据库事务和锁的相关知识。Select for update是用于在事务中锁定行,防止其他事务修改这些行,直到当前事务提交或回滚。 那如果在一个事务里多次对同一行数据执行Select for update,会发生什么呢?首先,第一次执行的时候,事务会获取该行的排他锁。这时候其他事务如果想修改这行或者也执行Select for update,就会被阻塞,直到当前事务结束。 但同一个事务内部再次执行同样的Select for update,会不会有问题?比如会不会出现死锁或者锁升级?根据我的记忆,在大多数数据库系统中,比如MySQL,同一个事务内重复获取同一行的锁是允许的,不会导致阻塞。因为事务已经持有该锁,再次请求时数据库会识别到锁已经被当前事务持有,所以不会重复加锁,也不会报错。 不过需要考虑数据库的具体实现。比如,在MySQL的InnoDB中,行级锁是在事务结束时才释放的。如果在同一个事务里多次Select for update同一行,每次都会确认锁的存在,但不会冲突。但如果是不同的行,或者不同的条件,可能会有不同的情况。 还有可能用户关心的是这样重复加锁会不会影响性能。虽然数据库会处理这种情况,但多次执行相同的锁定语句可能增加不必要的开销,应该避免冗余操作。 另外,如果在重复执行时,中间有其他操作,比如更新该行数据,这时候再次Select for update,锁的状态会不会变化?可能需要测试一下,但一般来说,锁的持有会持续到事务结束,无论中间是否有更新操作。 总结的话,同一个事务内多次Select for update同一行不会有问题,但应该避免不必要的重复操作,以确保性能和代码清晰。同时要注意不同数据库的具体实现可能略有差异,需要参考对应文档。</think>好的,我来逐步解释事务内重复执行`SELECT FOR UPDATE`对单条数据行的影响: 1. **基本机制** - `SELECT FOR UPDATE`会对查询到的数据行加**排他锁(X锁)**,阻止其他事务修改或加锁。 - 在事务提交或回滚前,锁会一直持有。 2. **同一事务内重复执行的情况** - **第一次执行**:成功获取该行的排他锁。 - **后续重复执行**: - 数据库会识别到当前事务已持有该锁,**不会重复加锁**。 - 不会阻塞或报错,直接返回当前数据。 - 例如在MySQL中表现为空操作,在Oracle/PG中可能更新锁的"新鲜度"。 3. **对其他事务的影响** ```sql -- 事务A START TRANSACTION; SELECT * FROM table WHERE id=1 FOR UPDATE; -- 加锁成功 SELECT * FROM table WHERE id=1 FOR UPDATE; -- 仍然有效 COMMIT; -- 事务B(同时发起) START TRANSACTION; SELECT * FROM table WHERE id=1 FOR UPDATE; -- 被阻塞直到事务A结束 ``` 4. **需要特别注意的场景** - **锁升级**:当重复锁定范围扩大时(如`WHERE id>1`),可能触发锁升级 - **死锁风险**:如果不同事务以乱序锁定多行 - **锁超时**:等待锁的事务可能因超过`innodb_lock_wait_timeout`而失败 5. **最佳实践建议** - 避免不必要的重复锁定 - 尽量缩小锁定范围 - 事务应保持简短 - 使用`NOWAIT`(如Oracle/PG)可避免阻塞 总结:同一事务内重复锁定单行是安全的,但需注意整体事务设计对系统并发性的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值