文章目录
- [1. 锁](#1. 锁)
- [2. 死锁](#2. 死锁)
1. 锁
数据库锁是一种用于管理并发访问数据库资源的机制,以确保数据的一致性和完整性。它通过限制对数据的访问来防止多个事务同时修改同一数据,从而避免数据冲突和不一致。后文提到的 MySQL 不强调的话,默认指的是使用 InnoDB 存储引擎的情况。
1.1 按粒度分
全局锁
全局锁是什么?
MySQL 全局锁是一种锁定整个数据库实例的机制,通常用于备份和维护操作。全局锁会阻止所有其他会话对数据库进行写入操作,直到锁被释放。
全局锁有哪些?
全局锁的常见形式是全局读锁(读锁),用于确保在备份期间没有写操作。它允许其他会话进行读操作,但阻止所有写操作。
虽然全局锁没有直接的全局写锁(写锁)命令,但可以通过锁定所有表来模拟全局写锁的效果,它用于阻止其他用户读取和更新数据。例如这样:
sql
-- 锁定所有表
LOCK TABLES my_table WRITE, another_table WRITE;
-- 执行写操作
UPDATE my_table SET column1 = 'value1' WHERE id = 1;
INSERT INTO another_table (column1) VALUES ('value2');
-- 释放锁
UNLOCK TABLES;
什么是会话?
在数据库管理系统(DBMS)中,会话(Session)是指客户端与数据库服务器之间的连接。每个会话代表一个独立的连接,通常由一个用户或应用程序发起。会话的生命周期从连接建立开始,到连接关闭为止。在会话期间,用户可以执行各种数据库操作,如查询、插入、更新和删除数据。
会话的特点?
- 独立性:每个会话是独立的,操作不会直接影响其他会话。
- 事务管理:会话可以管理事务,确保数据操作的原子性、一致性、隔离性和持久性(ACID)。
- 资源分配:每个会话占用一定的数据库资源,如内存、CPU 和连接池。
举个例子:
以 Flask-SQLAlchemy 为例,
db.session
是用于与数据库进行交互的会话对象。它的范围通常是一个请求(request)的生命周期。每个请求都会创建一个新的会话,当请求结束时,会话会被提交或回滚,并最终关闭。而在非请求的代码片段中需要显示地开启会话、提交或回滚事务,以及关闭会话。
全局锁有什么用?
从开始备份到备份结束的期间,数据库中的数据可能会由于用户的操作而发生改变,导致我们本意是想备份开始备份时间节点上的数据,却错误的备份到在该节点之后的数据,数据不一致。
如果有全局锁岂不是意味着我在备份线上的数据库时,用户无法更新数据吗?
MySQL 自带的mysqldump
支持在备份过程中允许数据更新,这可以通过使用 --single-transaction
选项来实现。这个选项会在备份开始时启动一个事务,以确保备份的一致性,同时允许其他事务继续进行读写操作。
全局锁怎么用?
sql
-- 获取全局读锁
FLUSH TABLES WITH READ LOCK;
-- 在获取锁后执行备份操作
-- 例如,使用 mysqldump 工具进行备份
-- mysqldump -u root -p --all-databases > backup.sql
-- 释放全局读锁
UNLOCK TABLES;
全局锁有什么风险?
- 性能影响:全局锁会阻塞所有其他事务,导致数据库在锁定期间无法处理正常的读写操作,可能会严重影响应用的性能和可用性。
- 高延迟:在全局锁定期间,所有等待的事务都会积压,解锁后可能会导致瞬时的高延迟和性能瓶颈。
- 不适合高并发环境:在高并发环境中,全局锁的使用会导致大量事务等待,影响系统的整体性能和用户体验。
表级锁
表级锁是什么?
表级锁(Table-Level Lock)是数据库管理系统(DBMS)中一种锁定机制,用于控制对整个表的并发访问。表级锁可以防止多个事务同时对同一张表进行冲突操作,从而确保数据的一致性和完整性。MyISAM 存储引擎主要使用表级锁来管理并发访问。
表级锁有哪些?
- 表共享读锁(表读锁):对一张表来说,假如一个事务获取了它的读锁,那么它会允许该事务进行读取操作,而不允许其他任何事务进行写操作,但是可以进行读操作。也就是说,多个事务可以同时获取读锁,读锁之间是不会互相阻塞的。
- 表独占写锁(表写锁):对一张表来说,假如一个事务获取了它的写锁,那么它会允许该事务进行读取和写入操作,而不允许其他任何事务进行读取和写入操作。也就是说,只有一个事务可以获取写锁,写锁会阻塞其他所有锁,包括读锁和写锁。
表级锁有什么用?
首先我们需要了解表级锁有哪些特点:
- 锁定范围大:表级锁会锁定整个表,任何对该表的读写操作都需要等待锁释放。
- 低并发性:由于锁定整个表,多个事务无法同时对表进行并发操作,可能导致性能瓶颈。
- 开销小:锁定粒度较粗,管理和实现相对简单。
基于上面的一些特点,表级锁有自己特定的使用场景:
- 多读少写:如果我们的应用主要进行读取操作,很少进行写入操作,或者可以容忍写操作的延迟,那么使用表级锁是不错的选择。因为表级锁的读锁不会阻塞其他的读锁,这种场景下表级锁可以简化锁管理,提高读取性能。
- 批量操作多:在进行大规模的批量插入、更新或删除操作时,使用表级锁可以避免频繁的行级锁定和释放,提高操作效率。
- 低并发:数据冲突的情况少,简化锁管理而不会显著影响性能。
表级锁怎么用?
LOCK TABLES
命令可以显示地为一个或者多个表加上读锁和写锁:
sql
-- 事务1:获取表级共享锁
START TRANSACTION;
LOCK TABLES products READ;
SELECT * FROM products;
-- 事务1在读取数据时,其他事务可以读取但不能写入
UNLOCK TABLES;
COMMIT;
-- 事务2:获取表级排他锁
START TRANSACTION;
LOCK TABLES products WRITE;
INSERT INTO products (name, price) VALUES ('New Product', 100);
-- 事务2在写入数据时,其他事务不能读取或写入
UNLOCK TABLES;
COMMIT;
除了显示加锁,还有一些命令会触发表级锁,例如ALTER TABLE
、DROP TABLE
、CREATE TABLE
、TRUNCATE TABLE
、RENAME TABLE
等。
表级锁有什么风险?
- 性能下降:因为表锁会锁定整个表,所以在高并发的环境中,它可能导致大量的请求阻塞,从而降低性能。对于读取和写入混合密集的应用,表锁可能会成为一个性能瓶颈。
- 并发性能差:一旦一个线程对表获得了写锁,其他线程的任何读写操作都会被阻塞,直到写锁被释放。同样的,如果一个读锁被持有,那么其他的写操作都将被阻塞。这就使得并发性能大大降低。
- 锁等待和超时:在高并发环境中,由于表级锁的粒度较大,可能有很多线程在等待锁,如果等待时间过长,可能会导致锁超时,进一步影响应用的性能和可用性。
- 写操作影响大:如果一个长时间运行的写操作(例如大量数据在批量更新或插入)获取了写锁,那么会阻塞其他的读写操作。
- 死锁的可能:如果多个事务以不同的顺序获取多个表的锁,可能会导致死锁。例如:事务1获取表A的锁,然后尝试获取表B的锁。事务2获取表B的锁,然后尝试获取表A的锁。这时,事务1和事务2相互等待对方释放锁,导致死锁。另外, 使用
LOCK TABLES
命令显式锁定多个表时,如果多个事务以不同的顺序锁定表,也可能会导致死锁。
行级锁
行级锁是什么?
行级锁可以对数据库表中单独的一行进行锁定。相比于表级锁和页锁,行级锁的粒度更小,因此在处理高并发事务时,能提供更好的并发性能和更少的锁冲突。然而,行级锁也需要更多的内存和CPU资源,因为需要对每一行都进行管理。
行级锁有哪些?
- 共享锁(S锁):对一行数据来说,假如一个事务获取了它的读锁,那么它会允许该事务进行读取操作,而不允许其他任何事务进行写操作,但是可以进行读操作。也就是说,多个事务可以同时获取读锁,读锁之间是不会互相阻塞的。
- 排他锁(X锁):对一行数据来说,假如一个事务获取了它的写锁,那么它会允许该事务进行读取和写入操作,而不允许其他任何事务进行读取和写入操作。也就是说,只有一个事务可以获取写锁,写锁会阻塞其他所有锁,包括读锁和写锁。
在实际使用场景中,MySQL 还提供了一种名为"间隙锁"的特性。间隙锁不仅锁定一个具体的行,而且还锁定它前后的"间隙",即这一行之前的行和之后的行之间的空间。间隙锁可以防止其他事务插入新的行到已锁定的行前后,从而解决一些并发问题。
另外,行级锁只有在事务中有效,即只有在一个事务开始后并在事务提交或回滚之前,才能对数据进行锁定。如果在非事务换几个中执行 SQL 语句,MySQL 会在语句执行结束后,立即释放所有的锁。
行级锁有什么用?
首先我们需要了解行级锁有哪些特点:
- 细粒度锁定:行级锁只锁定特定的行,而不是整个表或数据页。这种细粒度的锁定方式可以显著提高并发性能。
- 高并发性:由于行级锁允许多个事务同时对不同的行进行操作,因此在高并发环境中,行级锁可以减少锁冲突,提高系统的吞吐量。
- 复杂性:行级锁的管理和实现相对复杂,因为需要跟踪和管理更多的锁对象。
基于上面的一些特点,行级锁有自己特定的使用场景:
- 高并发环境:在需要处理大量并发读写操作的场景中,行级锁可以显著提高系统的并发性能。
- 细粒度控制:在需要对特定行进行精细控制的场景中,行级锁可以提供更高的灵活性和控制力。
- 事务隔离:在需要确保事务隔离级别(如可重复读或串行化)的场景中,行级锁可以防止脏读、不可重复读和幻读。
- 短期锁:在需要对数据进行短时间锁定的情况下,行级锁可以防止长时间阻塞其他事务。
行级锁怎么用?
SELECT...FOR UPDATE
会对选定行添加一个排他锁,这意味着其他事务不能修改这些行,也不能对这些行添加共享锁。SELECT...LOCK IN SHARE MODE
会对选定的行添加一个共享锁,这意味着其他事务不能修改这些行,但是可以对这些行添加共享锁。- 除了以上两点,
INSERT
、UPDATE
、DELETE
都会触发排他锁。
注意,加锁的粒度和范围取决于WHERE
子句中用到的索引。如果用到了唯一索引,那么 MySQL 只会锁定匹配的行。如果没有用到唯一索引,那么 MySQL 可能会锁定更多的行,甚至是整个表,这就可能导致锁冲突和性能问题。
行级锁有什么风险?
- 死锁:例如事务1锁定了a行,并试图锁定b行,同时事务2锁定了b行并试图锁定a行,这就形成了死锁。MySQL 会检测到死锁并且终止其中一个事务,但这仍然可能导致性能问题和事务失败。
- 锁升级:如果一个事务试图锁定的行过多,又可能会从行级上升为表级,这就会导致更多的锁冲突。
- 锁等待:如果一个事务已经锁定了某行,其他试图访问这行的事务就必须等待,这就可能导致性能下降。如果有大量的事务在等待锁,就可能导致系统出现性能瓶颈。
- 资源消耗:行级锁需要更多的内存来存储锁信息,而且需要更多的 CPU 时间来处理锁请求和释放锁。如果数据库中的行数非常多,或者并发事务的数量非常多,这可能会导致显著的资源消耗。
- 难以调试和排查:由于行级锁额粒度很小,如果出现性能问题或者锁冲突,可能需要复杂的调试和排查工作来找出原因。
- 事务隔离级别:不同的事务隔离级别会影响锁的行为和性能,可能需要具体的业务场景来调整事务隔离级别。
1.2 按模式分
乐观锁
乐观锁是什么?
乐观锁是一种并发控制机制,假设多个事务在操作数据时不会发生冲突,因此不在操作开始时加锁,而是在提交数据时检查是否有冲突。如果检测到冲突,则回滚事务并重试。乐观锁通常用于读多写少的场景,因为在这种情况下,冲突的概率较低。
乐观锁的工作原理
在 MySQL 中,乐观锁并没有内置的实现,但是可以通过一些编程技巧来实现。常见的方式添加版本号或者时间戳字段。
详细的工作流程:
- 读取数据:读取数据时,获取当前的版本号或时间戳。
- 处理数据:应用程序对数据进行处理。
- 提交数据:在提交数据时,检查版本号或时间戳是否与读取时的一致。
- 如果一致,说明在处理期间没有其他事务修改数据,可以安全地提交更新,并递增版本号或更新时间戳。
- 如果不一致,说明在处理期间有其他事务修改了数据,检测到冲突,回滚事务并重试
乐观锁适用场景
- 低冲突环境:多数情况下,数据并发修改的冲突较低,即同一时间内,同一条数据不会被多个事务同时修改。如果冲突较多,那么乐观锁可能会导致大量的事务回滚,从而影响性能。
- 读多写少的场景:在读操作远多于写操作的情况下,乐观锁可以避免由于频繁读操作导致的不必要的锁定开销。
- 短事务操作:如果数据库的事务都是简短并且快速完成的,那么使用乐观锁可以减少因为等待锁而导致的时间消耗。
乐观锁的缺点
- 冲突检测:在高并发的环境中,乐观锁可能会导致大量的冲突。因为乐观锁只有在提交事务时才检查是否有冲突,如果多个事务在同一时间操作同一行数据,那么只有一个事务能提交成功,其他事务都需要回滚并且重新尝试。
- 处理开销:如果发生冲突,需要回滚和尝试,这可能会增加系统的开销。
- 版本管理:乐观锁通常需要通过版本号(或者时间戳)来检测冲突。这就要求系统能够正确地管理这些版本号,否则可能会导致错误地冲突检测。
- 编程复杂:使用乐观锁需要更复杂地编程,因为程序需要处理可能发成的冲突和重试。
悲观锁
悲观锁是什么?
悲观锁是一种并发控制机制,假设数据在操作过程中很可能会发生冲突,因此在操作开始时就对数据进行加锁,以防止其他事务对数据进行并发修改。悲观锁通常用于写多读少的场景,因为在这种情况下,数据冲突的概率较高。
悲观锁的工作原理
- 加锁:在操作开始时,事务对需要操作的数据进行加锁。加锁的类型可以是共享锁(读锁)或排他锁(写锁)。
- 操作数据:在加锁之后,事务可以安全地读取或修改数据,因为其他事务无法对被锁定的数据进行并发操作。
- 释放锁:在事务结束时(提交或回滚),释放锁,使其他事务可以访问和操作数据。
悲观锁的适用场景
- 写多读少:在写操作多于读操作的场景中,数据冲突的概率较高。使用悲观锁可以确保在操作过程中数据不会被其他事务修改,从而保证数据的一致性和完整性。
- 长事务:在长时间运行的事务中,数据被其他事务修改的概率较高。使用悲观锁可以防止数据在操作过程中被修改,确保事务的正确性。
- 高可靠性要求:在需要确保数据一致性和完整性的场景中,悲观锁可以提供更高的可靠性。例如,金融交易系统、库存管理系统等对数据一致性要求较高的应用场景。
- 需要严格控制并发:在某些业务场景中,需要严格控制并发操作,以确保业务逻辑的正确性。例如,银行账户转账操作需要确保在转账过程中账户余额不会被其他事务修改。
- 数据修改频繁:在数据修改频繁的场景中,使用悲观锁可以防止多个事务同时修改同一数据,从而避免数据不一致的问题。
悲观锁的缺点
- 性能开销大:悲观锁在操作开始时就对数据进行加锁,这会增加系统的锁开销。特别是在高并发环境中,频繁的加锁和解锁操作会显著影响系统性能。当一个事务持有锁时,其他需要访问相同数据的事务必须等待锁释放,这会导致较长的等待时间,影响系统的响应速度。
- 死锁:在高并发环境中,多个事务可能会相互等待对方释放锁,从而导致死锁。死锁会使相关事务无法继续执行,必须通过死锁检测和解决机制来处理,这增加了系统的复杂性。
- 并发性能低:由于悲观锁在操作开始时就对数据进行加锁,其他事务在锁释放之前无法访问被锁定的数据。这会限制系统的并发性能,特别是在需要高并发访问的场景中。
- 资源占用:长时间持有锁会占用系统资源,特别是在长事务的情况下。这会导致系统资源的浪费,影响其他事务的执行。
- 实现复杂性:使用悲观锁需要对应用程序进行额外的设计和实现,以确保正确的加锁和解锁操作。这增加了系统的复杂性和维护成本。
1.3 按状态分:意向共享锁与意向排他锁
意向锁是什么?
意向锁是一种用于提高数据库并发控制效率的锁机制,主要用于表级锁和行级锁之间的协调。意向锁的目的是在获取行级锁之前,先在表级上设置一个标记,表明某个事务打算对表中的某些行进行加锁操作。这样可以避免在获取行级锁时与其他事务的表级锁发生冲突。
举个例子,当有一个事务a有行锁时,MySQL 会自动为该表添加意向锁,事务b如果想要申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁,而直接判断是否存在意向锁即可,这样做可以提高性能。
虽然 MySQL 同时支持表级锁和行级锁,但也会发生冲突的时候。例如,假设一张表被事务a持有表级锁,而又被事务b持有某一行的行锁,相当于a可以修改被b锁定的一行数据,形成了冲突。所以意向锁的出现就可以避免这种情况。
意向锁的互斥与兼容
首先意向锁有三种类型:
- 意向共享锁(IS,Intent Shared Lock) :
- 表示事务打算在表中的某些行上获取共享锁(S锁)。
- 允许多个事务同时获取意向共享锁,但不允许获取表级排他锁(X锁)。
- 意向排他锁(IX,Intent Exclusive Lock) :
- 表示事务打算在表中的某些行上获取排他锁(X锁)。
- 允许多个事务同时获取意向排他锁,但不允许获取表级共享锁(S锁)或表级排他锁(X锁)。
- 共享意向排他锁(SIX,Shared Intent Exclusive Lock) :
- 表示事务已经在表上获取了共享锁(S锁),并打算在表中的某些行上获取排他锁(X锁)。
- 允许其他事务获取意向共享锁(IS),但不允许获取意向排他锁(IX)或表级排他锁(X锁)。
我们可以直观地用表格来展示这些锁之间的兼容与互斥:
IS | IX | S | X | SIX | |
---|---|---|---|---|---|
IS | Yes | Yes | Yes | No | Yes |
IX | Yes | Yes | No | No | No |
S | Yes | No | Yes | No | No |
X | No | No | No | No | No |
SIX | Yes | No | No | No | No |
1.4 按算法分
间隙锁
间隙锁是什么?
间隙锁是 MySQL InnoDB 存储引擎提供的一种锁定机制。它锁定的不是具体的行记录,而是两个索引之间的间隙(或者说区间),这样可以防止新的记录插入到该间隙,确保数据的一致性和事务的隔离性。
间隙锁的类型
需要注意的是,间隙锁常常与记录锁一起使用,形成 Next-Key 锁,保护索引记录的范围查询和扫描操作。
- 区间-区间:锁定两个索引键之间的间隙,或者是第一个索引键之前的间隙。
- 区间-记录间隙锁:锁定一个索引键和一个记录之间的间隙。
- 记录-区间间隙锁:锁定一个记录和一个索引键之间的间隙。
间隙锁有什么用
间隙锁的存在主要是为了解决幻读的问题。幻读是一种并发事务问题,发生在一个事务在两次读取操作之间,另一个事务插入或删除了数据,导致前后两次读取结果不一致。
举例来说,假设我们有一个存储学生信息的表,有一个事务A要查询年龄在10---20之间的学生,它在查询前会对这个区间加锁。此时如果有另一个事务B想要插入一个年龄为15的学生,由于这个年龄的范围已经被事务A锁定,所以事务B必须等待,直到事务A完成,释放锁。这样就避免了幻读的产生。
值得注意的是,由于间隙锁会锁定范围,如果并发事务较多且涉及的数据范围有交集,可能会引发性能问题,甚至死锁。因此,在设计数据库和选择隔离级别时,需要综合考虑数据一致性和并发性能。
间隙锁的适用场景
- 防止幻读。
- 范围查询:在执行范围查询时,如果事务需要对查询结果进行更新或删除,那么间隙锁可以保证在事务执行期间,不会有新的行插入到查询范围中。
间隙锁的缺点
- 锁开销:间隙锁会增加锁的开销,特别是在高并发环境中,频繁的加锁和解锁操作会显著影响系统性能。
- 锁范围大:间隙锁不仅锁定现有的行,还锁定行之间的间隙,这会导致锁的范围较大,增加锁管理的复杂性和开销。
- 锁争用问题:间隙锁可能会导致锁争用问题,影响其他事务的执行。例如,当一个事务持有间隙锁时,其他事务尝试在相同的间隙中插入新行会被阻塞,直到锁被释放。
- 降低并发性:由于间隙锁锁定了行之间的间隙,其他事务在这些间隙中进行插入操作时会被阻塞,从而降低系统的并发性能。
- 死锁:在高并发环境中,多个事务可能会相互等待对方释放锁,从而导致死锁。间隙锁增加了锁的范围和复杂性,可能会增加死锁的风险。
- 死锁检测和处理:数据库系统需要具备有效的死锁检测和处理机制,以避免和解决死锁问题,这增加了系统的复杂性。
- 特定场景:间隙锁主要用于防止幻读,在不涉及幻读问题的场景中,间隙锁的优势不明显。
记录锁
- 定义:记录锁是锁定单个索引记录的锁。它用于确保在并发环境中对单个记录的读取和修改操作的一致性。
- 锁定范围:记录锁仅锁定特定的索引记录,不影响其他记录或间隙。
- 用途:记录锁主要用于防止多个事务同时修改同一条记录,从而确保数据的一致性。
临键锁
- 定义:临键锁是记录锁和间隙锁的组合,既锁定实际的行,也锁定行之间的间隙。通过这种方式,临键锁可以防止其他事务在锁定的行和间隙中插入、更新或删除数据,从而确保读取数据的一致性。
- 锁定范围:临键锁锁定的是一个索引记录和它前面的间隙的组合。这样既锁定了实际的行,也锁定了行之间的间隙。
- 用途:临键锁主要用于防止幻读,确保在一个事务中读取的数据在整个事务期间保持一致。
间隙锁、记录锁、临键锁示例
假设有一个名为 orders
的表,表结构如下:
sql
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
amount DECIMAL(10, 2)
);
假设表中有以下初始数据:
sql
INSERT INTO orders (order_id, customer_id, amount) VALUES
(1, 1, 100.00),
(3, 1, 150.00),
(5, 2, 200.00);
记录锁(Record Lock)示例
记录锁仅锁定特定的索引记录。
sql
-- 事务1:获取记录锁
START TRANSACTION;
SELECT * FROM orders WHERE order_id = 3 FOR UPDATE;
-- 事务1锁定 order_id 为 3 的记录
-- 事务2:尝试修改相同记录
START TRANSACTION;
UPDATE orders SET amount = 175.00 WHERE order_id = 3;
-- 事务2会被阻塞,直到事务1释放记录锁
-- 事务1:提交事务,释放锁
COMMIT;
-- 事务2:现在可以修改记录
UPDATE orders SET amount = 175.00 WHERE order_id = 3;
COMMIT;
间隙锁(Gap Lock)示例
间隙锁锁定行之间的间隙,不锁定具体的行。
sql
-- 事务1:获取间隙锁
START TRANSACTION;
SELECT * FROM orders WHERE order_id BETWEEN 1 AND 5 FOR UPDATE;
-- 事务1会锁定 order_id 为 1 和 5 之间的所有间隙
-- 包括 (1, 3), (3, 5) 和 (5, ∞)
-- 事务2:尝试在间隙中插入新行
START TRANSACTION;
INSERT INTO orders (order_id, customer_id, amount) VALUES (4, 1, 175.00);
-- 事务2会被阻塞,直到事务1释放间隙锁
-- 事务1:提交事务,释放锁
COMMIT;
-- 事务2:现在可以插入新行
INSERT INTO orders (order_id, customer_id, amount) VALUES (4, 1, 175.00);
COMMIT;
临键锁(Next-Key Lock)示例
临键锁是记录锁和间隙锁的组合,既锁定实际的行,也锁定行之间的间隙。
sql
-- 事务1:获取临键锁
START TRANSACTION;
SELECT * FROM orders WHERE order_id = 3 FOR UPDATE;
-- 事务1会锁定 order_id 为 3 的行和 (1, 3), (3, 5) 之间的间隙
-- 事务2:尝试在间隙中插入新行
START TRANSACTION;
INSERT INTO orders (order_id, customer_id, amount) VALUES (4, 1, 175.00);
-- 事务2会被阻塞,直到事务1释放临键锁
-- 事务1:提交事务,释放锁
COMMIT;
-- 事务2:现在可以插入新行
INSERT INTO orders (order_id, customer_id, amount) VALUES (4, 1, 175.00);
COMMIT;