PostgreSQL

关注公众号 jb51net

关闭
首页 > 数据库 > PostgreSQL > mysql锁与隔离级别

PostgreSQL与MySQL的锁与隔离级别操作方法

作者:东南门扫雪

文章详细介绍了PostgreSQL和MySQL在锁机制上的区别,包括锁的粒度、实现方式、核心锁类型、死锁处理方式、实际使用场景、锁监控和诊断以及性能影响,文章还比较了两种数据库在不同场景下的适用性,并给出了选择建议,感兴趣的朋友跟随小编一起看看吧

问:postgresql delete from table_name 这样的sql会锁表的哪些操作,写在存储过程中的锁粒度与单独执行有什么区别?

答:

已深度思考(用时9秒)

在PostgreSQL中,DELETE FROM table_name语句会锁定被删除的行,并且可能还会在表上获取锁。具体锁的类型和粒度取决于多个因素,包括事务隔离级别、是否使用了子查询以及是否有外键约束等。

存储过程中的锁粒度与单独执行的区别

在存储过程中执行DELETE语句,其锁行为与单独执行DELETE语句在锁的粒度上通常没有区别。因为锁的粒度是由SQL语句本身和事务的隔离级别决定的,而不是由是否在存储过程中执行决定的。

但是,存储过程可能会将多个SQL语句封装在一个事务中(取决于存储过程的定义和调用方式),这可能会影响锁的持有时间。如果在存储过程中,DELETE语句只是一个大事务的一部分,那么锁可能会在整个事务期间持有,而不是在语句执行后立即释放。这可能会增加锁竞争的风险,因为锁被持有的时间更长了。

另外,存储过程中可能包含多个操作,这些操作可能会获取多个锁,从而增加死锁的可能性。

示例

假设我们有一个存储过程,它执行一个删除操作,然后执行其他一些操作:

CREATE OR REPLACE PROCEDURE delete_data()
LANGUAGE plpgsql
AS $$
BEGIN
    DELETE FROM table_name WHERE condition;
    -- 其他操作...
END;
$$;

如果在调用这个存储过程时,我们是在一个事务中(比如使用BEGINCOMMIT),那么删除操作所获取的锁会一直持有到事务结束。而如果单独执行DELETE语句,并且是自动提交的,那么锁会在语句执行后立即释放。

因此,区别主要在于锁的持有时间,而不是锁的粒度。锁的粒度(行锁和表锁)是由SQL语句本身决定的。

总结

在设计存储过程时,应该注意事务的边界,尽量避免长时间持有锁,以减少对并发操作的影响。

很好,这是一个关于 PostgreSQL 并发控制和锁机制的核心问题。我们来详细拆解一下。

第一部分:DELETE FROM table_name会锁住哪些操作?

当你执行 DELETE FROM table_name(不带任何 WHERE条件)时,由于它要删除表中的所有行,PostgreSQL 会采取比较严格的锁策略来保证数据完整性。

该语句会获取的表级锁主要是:ACCESS EXCLUSIVE

ACCESS EXCLUSIVE是 PostgreSQL 中最高级别的锁,与大部分其他锁都冲突。一旦一个事务持有了这个锁,其他事务尝试获取任何其他锁(即使是普通的读锁)都会被阻塞。

具体来说,DELETE FROM table_name会阻塞其他会话的以下操作:

反之,当 DELETE FROM table_name正在执行时,它也会被以下操作阻塞:

重要例外:带 WHERE条件的 DELETE

如果你的 DELETE语句包含一个非常具体的 WHERE条件(例如 WHERE id = 123),PostgreSQL 会尽量使用更细粒度的锁。它会先获取一个较弱的 ROW EXCLUSIVE 表锁,然后只在它实际要删除的那些行上设置 EXCLUSIVE 行锁。在这种情况下:

第二部分:存储过程中的锁粒度与单独执行的区别

核心结论:锁的粒度(锁定范围)本身没有区别,但锁的持有时间、事务的边界以及发生死锁的风险可能有显著差异。

锁的粒度(是锁表还是锁行)是由 SQL 语句本身(如 DELETE)和当时的具体情况(有无索引、WHERE 条件是否高效等)决定的,与这条语句是在存储过程(函数)中执行还是单独执行无关

它们的主要区别在于事务的上下文和管理:

特性单独执行 SQL在存储过程(函数)中执行
事务边界默认是自动提交(Autocommit)。每条语句都是一个独立的事务。取决于调用方式。如果存储过程用 BEGIN...END块调用,则它通常是外部事务的一部分。如果使用 CALL直接调用,存储过程本身可以是一个事务(如果函数被定义为 PROCEDURE且内部有事务控制)或部分事务。
锁持有时间锁在语句执行完毕后(事务提交或回滚时)立即释放。锁可能被持有更长时间。因为存储过程可能包含多个 SQL 操作,锁会从第一条加锁语句开始持有,直到整个存储过程执行完毕且外部事务提交/回滚后才释放。
死锁风险相对较低。因为单个语句执行很快,持有锁的时间很短。相对较高。由于锁持有时间变长,多个会话执行同一个存储过程时,更容易形成“互相等待对方持有的锁”的死锁局面。
错误处理与锁如果语句执行失败,事务回滚,锁立即释放。复杂的存储过程可能有 EXCEPTION块。在异常块中,发生错误后的事务状态是“子事务”,这可能会让锁的行为对开发者来说更复杂。

举例说明锁持有时间的区别:

场景1:单独执行

-- 会话 A
DELETE FROM orders WHERE status = 'expired'; -- 获取锁
-- 语句执行很快,比如 100ms,锁随即释放。
INSERT INTO audit_log VALUES ('Cleaned expired orders');
-- 这是另一个事务,重新获取锁。

在这个场景中,锁只被持有了很短的时间。

场景2:在存储过程中执行

假设有一个存储过程 cleanup_data()

CREATE OR REPLACE PROCEDURE cleanup_data()
LANGUAGE plpgsql
AS $$
BEGIN
    -- 步骤1:删除过期订单
    DELETE FROM orders WHERE status = 'expired'; -- 获取锁
    -- 锁被持有中...
​
    -- 步骤2:一个很慢的复杂计算或对其他系统的调用
    PERFORM pg_sleep(10); -- 模拟一个耗时 10 秒的操作
​
    -- 步骤3:记录日志
    INSERT INTO audit_log VALUES ('Cleaning completed');
    COMMIT; -- 或者取决于调用方式,事务在此提交
END;
$$;
-- 在会话中调用
CALL cleanup_data();

在这个场景中,DELETE操作获得的锁会被持续持有长达 10 秒以上(直到整个存储过程执行完毕)。在这 10 多秒内,其他尝试修改 orders表相关行的事务都会被阻塞,大大增加了并发冲突和死锁的风险。

总结与最佳实践

希望这个详细的解释能帮助你更好地理解 PostgreSQL 中的锁机制。

问:truncate是属于表结构修改语句吗

答:

已深度思考(用时3秒)

这是一个关于PostgreSQL中TRUNCATE命令分类的问题。需要明确TRUNCATE在数据库操作中的分类,并解释其特性。

首先回顾TRUNCATE的功能:快速删除表中所有数据。与DELETE相比,TRUNCATE是DDL(数据定义语言)操作,而DELETE是DML(数据操作语言)操作。

接下来分析TRUNCATE是否属于表结构修改语句。虽然TRUNCATE不改变表的结构(如列定义、约束等),但它会重置与表相关的序列、立即释放磁盘空间等,这些特性更接近DDL操作。

然后考虑TRUNCATE的锁行为。与DELETE的ROW EXCLUSIVE锁或ACCESS EXCLUSIVE锁不同,TRUNCATE需要ACCESS EXCLUSIVE锁,这会阻塞所有其他操作。

最后总结TRUNCATE的分类依据:尽管不修改表结构,但因其DDL特性和锁级别,通常被归类为DDL操作,也就是表结构修改语句的范畴。

这是一个很好的问题,答案有点微妙。

严格来说,TRUNCATE 不被归类为典型的“修改表结构”的 DDL 语句,但它确实具有很多 DDL 的特性,并且在锁的层面上,它表现得比大多数 DDL 更“霸道”。

我们可以从以下几个层面来理解:

1. 从 SQL 标准分类来看

TRUNCATE的核心功能是删除数据,所以从目的上看,它更像 DML。然而,它的实现机制却非常接近 DDL。

2. TRUNCATE 与 DELETE 的本质区别(这解释了它的分类)

特性DELETETRUNCATE
操作机制逐行删除。它在事务日志中为每一行删除操作都记录一条记录。直接回收数据页。它通过取消分配存储数据的磁盘页来工作,只在日志中记录“页已释放”,而不是每一行。
性能慢,尤其是大表。因为需要记录每一行的删除。极快。因为它不关心表里有多少数据,只是直接删除数据文件。
事务性是 DML,完全受事务控制。可以 ROLLBACK在 PostgreSQL 中,是事务性的,可以 ROLLBACK。但请注意,并非所有数据库都这样(比如 Oracle 中 TRUNCATE是隐式提交的,不能回滚)。
触发器会触发表上的 BEFORE DELETEAFTER DELETE触发器。不会触发任何 ON DELETE触发器。
存储空间被删除行占用的空间只是被标记为可重用,但不会立即返还给操作系统(需要 VACUUM)。立即将磁盘空间返还给操作系统(除了在事务中执行且未提交的情况)。
WHERE 条件支持,可以删除部分数据。不支持,只能清空整个表。

从实现机制可以看出,TRUNCATE更像是在操作表的“存储结构”,而不是在逻辑上一条条删除数据。这种直接操作底层数据文件的方式,是 DDL 的典型特征。

3. 锁的层面(这与你的第一个问题直接相关)

TRUNCATE会获取最高级别的 ACCESS EXCLUSIVE锁。

这与最典型的 DDL 语句(如 ALTER TABLE ... ADD COLUMN)所需的锁级别完全一样。这意味着:

所以,在锁的行为上,TRUNCATE和 DDL 语句是完全一致的。 这也是为什么很多人倾向于把它看作一种特殊的 DDL 命令。

结论

简单来说:你可以认为 TRUNCATE是一个“披着 DML 外衣的 DDL”,尤其是在讨论锁和性能时。

问:数据库有默认隔离级别 那每执行的一条语句可以指定单独的隔离级别吗 隔离级别只针对查询语句吗

答:

已深度思考(用时1秒)

我们讨论的是PostgreSQL数据库。首先,回答两个问题:

下面详细解答。

这是一个非常好的问题,涉及到事务隔离级别的核心概念。我们来详细解答。

问题一:可以每条语句单独指定隔离级别吗?

答案是:不可以直接为单条语句指定隔离级别,但可以通过控制事务边界来“模拟”实现类似效果。

隔离级别(Isolation Level)是事务(Transaction) 的属性,而不是单条语句的属性。一个事务可以包含多条语句,这些语句都运行在同一个隔离级别下。

正确的做法是:在事务开始时设置隔离级别。

示例:在事务中设置隔离级别

-- 开始一个事务,并设置隔离级别为 REPEATABLE READ
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
​
-- 在这个事务内执行多条语句,它们都处于 REPEATABLE READ 级别
SELECT * FROM accounts WHERE user_id = 1;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
INSERT INTO transactions (user_id, amount) VALUES (1, -100);
​
-- 提交事务,隔离级别设置也随之结束
COMMIT;

如何实现“每条语句不同隔离级别”的效果?

虽然不能直接为语句设置,但你可以通过将每条语句封装在独立的小事务中来近似实现:

-- 语句1:在 READ COMMITTED 级别下执行
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
DELETE FROM temp_log WHERE created_at < now() - interval '1 day';
COMMIT;
​
-- 语句2:在 REPEATABLE READ 级别下执行
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT SUM(balance) FROM accounts; -- 确保在事务期间读到一致的数据快照
COMMIT;
​
-- 语句3:在 SERIALIZABLE 级别下执行(最严格,防止幻读)
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO orders (user_id, total) 
SELECT user_id, SUM(amount) FROM cart WHERE user_id = 123;
DELETE FROM cart WHERE user_id = 123;
COMMIT;

注意:这种方式的缺点是每个语句都成为一个独立的事务,可能会增加开销,并且无法将多个操作原子化(即一个失败不能全部回滚)。

问题二:隔离级别只针对查询语句吗?

答案是:绝对不是。隔离级别影响事务内的所有操作,包括读(SELECT)和写(INSERT, UPDATE, DELETE)。

隔离级别定义了事务在并发环境下如何与其他事务交互,它主要解决以下经典并发问题:

这些问题不仅涉及“读”,也涉及“写”。隔离级别通过控制锁机制或多版本并发控制(MVCC)来管理这些交互。

隔离级别对写操作的影响示例

假设有两个并发的事务:

事务 A(在 REPEATABLE READ 级别下)

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 第一次查询,count = 10
SELECT COUNT(*) FROM products WHERE category = 'electronics';

此时,事务 B 插入了一条新的电子产品记录并提交。

-- 事务 B (已提交)
INSERT INTO products (name, category) VALUES ('New Phone', 'electronics');
COMMIT;

现在回到事务 A

-- 在 REPEATABLE READ 级别下,第二次查询 count 仍然是 10(幻读被防止)
-- 因为事务 A 看到的是事务开始时的数据快照。
SELECT COUNT(*) FROM products WHERE category = 'electronics';
​
-- 但是,如果事务 A 尝试更新所有电子产品...
UPDATE products SET price = price * 0.9 WHERE category = 'electronics';
-- 问题来了:这条 UPDATE 语句会更新多少行?
-- 在 PostgreSQL 的可重复读级别下,它会检测到“写倾斜”并抛出序列化失败错误。
-- 因为它发现它要更新的行数(10行)与它当前快照中看到的不一致(因为事务B插入了新行)。

这个例子说明,隔离级别不仅影响了事务 A 的 SELECT查询,也直接影响了它的 UPDATE操作的行为,甚至可能导致操作失败。

PostgreSQL 各隔离级别对读写操作的影响总结

隔离级别脏读不可重复读幻读对写操作的影响
读未提交可能发生可能发生可能发生写操作可能基于未提交的脏数据,极其不安全。(PostgreSQL 实际上不提供此级别,它被提升为读已提交)
读已提交不可能可能发生可能发生写操作只会阻塞在正在修改的行上,并发性好。这是 PostgreSQL 的默认级别
可重复读不可能不可能可能发生*能防止幻读。但如果事务检测到其计划修改的数据在它开始后被其他已提交事务修改过,会抛出序列化失败错误,要求应用程序重试整个事务。
可序列化不可能不可能不可能最严格级别。通过乐观锁机制,如果检测到任何可能破坏序列化执行顺序的风险,都会抛出序列化失败错误,要求重试。

*在 PostgreSQL 中,REPEATABLE READ通过快照隔离实现,实际上也防止了幻读。但标准 SQL 允许该级别出现幻读,所以 PostgreSQL 的行为比标准更严格。

总结

脏读(Dirty Read):一个事务读到了另一个未提交事务修改的数据。

不可重复读(Non-repeatable Read):同一个事务内,两次读取同一行数据,结果不一致(因为被其他已提交事务修改了)。

幻读(Phantom Read):同一个事务内,两次执行相同的查询,返回的结果集不一致(因为其他已提交事务插入了新数据)。

问:mysql在可重复读隔离级别下不能解决幻读吗

答:

已深度思考(用时6秒)

我们已知MySQL的默认隔离级别是REPEATABLE READ。在MySQL中,REPEATABLE READ隔离级别通过多版本并发控制(MVCC)和间隙锁(Gap Locks)来防止幻读。

但是,需要注意的是,标准的SQL隔离级别定义中,REPEATABLE READ是不要求防止幻读的,而只要求防止不可重复读。然而,MySQL的InnoDB引擎在REPEATABLE READ隔离级别下,通过间隙锁机制确实可以防止幻读。这是MySQL对标准隔离级别的扩展。

具体来说:

因此,在MySQL的InnoDB引擎中,可重复读隔离级别可以防止幻读。但是,这取决于你使用的存储引擎,只有InnoDB支持间隙锁和MVCC。

注意:在MySQL中,如果使用其他存储引擎(如MyISAM),则可能无法防止幻读。

所以,回答是:MySQL的InnoDB引擎在可重复读隔离级别下可以防止幻读。但是,这并非SQL标准的要求,而是MySQL的增强。

总结一下:

但是,需要注意的是,在可重复读隔离级别下,如果事务中先执行了一次普通SELECT,然后另一个事务插入并提交了新数据,接着当前事务再执行相同的普通SELECT,是不会看到新数据的(因为快照读)。但是,如果当前事务执行了一个当前读(比如SELECT ... FOR UPDATE),那么它会看到最新的数据,并且会阻塞直到另一个事务释放锁,或者使用间隙锁来防止插入,从而避免幻读。

因此,在MySQL中,可重复读隔离级别可以有效地防止幻读。

这是一个非常好的问题,也是 MySQL 隔离级别中一个很重要的知识点。

简单回答:MySQL 在可重复读(REPEATABLE READ)隔离级别下,通过 Next-Key Locking 机制,在很大程度上可以防止幻读。

这与 PostgreSQL 的行为不同,也是 MySQL 的一个特点。

详细解释

1. 标准 SQL 规范 vs MySQL 实现

2. MySQL 如何防止幻读

MySQL 使用两种机制结合来防止幻读:

机制一:快照读(Snapshot Read) - 基于 MVCC

对于普通的 SELECT语句(非加锁读):

-- 事务 A
BEGIN;
SELECT * FROM users WHERE age > 20; -- 第一次读取,返回 5 行
-- 此时事务 B 插入了一条 age=25 的新记录并提交
SELECT * FROM users WHERE age > 20; -- 第二次读取,仍然返回 5 行(看不到新插入的数据)

这种情况下,基于 MVCC 的多版本控制,事务始终读取事务开始时的数据快照,自然就看不到其他事务新插入的数据。

机制二:当前读(Current Read) - 基于 Next-Key Locking

对于加锁的读操作(SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE)和写操作(UPDATE, DELETE):

MySQL 使用 Next-Key Lock = Record Lock(记录锁) + Gap Lock(间隙锁)

示例说明:

-- 表 users 有索引在 age 上,现有数据:age = 10, 20, 30, 40
​
-- 事务 A
BEGIN;
SELECT * FROM users WHERE age = 25 FOR UPDATE; -- 没有 age=25 的记录

此时,MySQL 不仅会锁住 age=20 和 age=30 之间的间隙(Gap Lock),还会锁住下一条记录(age=30)的 Record Lock,这就是 Next-Key Lock。

现在事务 B 尝试插入:

-- 事务 B(在另一个会话中)
INSERT INTO users (age) VALUES (25); -- 这个操作会被阻塞!
INSERT INTO users (age) VALUES (22); -- 这个也会被阻塞!
INSERT INTO users (age) VALUES (28); -- 这个也会被阻塞!

任何尝试插入 age 在 20 到 30 之间的记录都会被阻塞,直到事务 A 提交。这样就防止了幻读。

3. 不同操作场景下的幻读防止

操作类型是否防止幻读机制
普通 SELECT✅ 防止MVCC 快照读,始终读取一致的数据视图
SELECT ... FOR UPDATE✅ 防止Next-Key Locking 锁住记录和间隙
SELECT ... LOCK IN SHARE MODE✅ 防止Next-Key Locking 锁住记录和间隙
UPDATE/DELETE✅ 防止Next-Key Locking 锁住记录和间隙
INSERT✅ 防止唯一性检查时会使用间隙锁防止冲突

4. 实际测试演示

-- 会话 A
mysql> BEGIN;
mysql> SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE;
-- 返回空结果集
​
-- 会话 B(尝试插入会被阻塞)
mysql> INSERT INTO users (name, age) VALUES ('新用户', 25);
-- 这个操作会一直等待,直到会话 A 提交或超时

5. 与 PostgreSQL 的对比

特性MySQL (REPEATABLE READ)PostgreSQL (REPEATABLE READ)
幻读防止✅ 完全防止✅ 防止(通过快照隔离)
机制Next-Key Locking + MVCC纯 MVCC(快照隔离)
写冲突处理阻塞等待可能抛出序列化错误,需要重试
锁粒度行锁 + 间隙锁主要依赖快照,较少使用锁

6. 需要注意的例外情况

虽然 MySQL 在可重复读级别下能防止大部分幻读,但在以下情况下仍需注意:

总结

MySQL 在可重复读隔离级别下确实可以有效防止幻读,这是通过 Next-Key Locking 机制实现的。这种实现比 SQL 标准的要求更严格,为开发者提供了更强的数据一致性保证。

这也是为什么很多应用程序选择使用 MySQL 的可重复读级别而不是串行化级别的原因——它在保证强一致性的同时,提供了更好的并发性能。

问: pgsql的锁类型与mysql的区别

答:

已深度思考(用时16秒)

PostgreSQL 和 MySQL 在锁机制上有显著的不同,这主要是由于它们的设计哲学和架构差异。下面我们来详细比较一下。

PostgreSQL 的锁类型与特点

PostgreSQL 提供了多粒度的锁机制,包括:

PostgreSQL 使用多版本并发控制(MVCC)来避免读写冲突,因此读操作不会阻塞写操作,写操作也不会阻塞读操作(除了某些特殊情况,如 ACCESS EXCLUSIVE锁会阻塞读)。

MySQL 的锁类型与特点(以 InnoDB 为例)

MySQL 的 InnoDB 存储引擎也支持行级锁和表级锁,但实现方式与 PostgreSQL 有差异:

主要区别

使用建议

总结

PostgreSQL 和 MySQL 在锁机制上各有特点。PostgreSQL 的锁种类更丰富,且与 MVCC 结合更紧密,读不阻塞写,写不阻塞读。MySQL 的 InnoDB 通过意向锁、行锁、间隙锁等机制来实现多粒度锁,在可重复读隔离级别下通过间隙锁防止幻读。选择哪种数据库取决于具体应用场景和对并发控制的需求。

PostgreSQL 和 MySQL 在锁机制上有显著的区别,主要体现在锁的粒度、实现方式、锁类型和管理策略上。以下是详细的对比分析:

1. 锁粒度对比

锁粒度PostgreSQLMySQL (InnoDB)
表级锁支持,但较少使用支持,有更多表锁类型
行级锁支持,主要依赖 MVCC支持,通过行锁+间隙锁
页级锁不支持不支持(早期 MyISAM 支持)
意向锁隐式管理显式支持(IS、IX 锁)
间隙锁不支持支持(防止幻读的关键)

2. 核心锁类型差异

PostgreSQL 的主要锁类型:

-- 表级锁(8种主要类型)
ACCESS SHARE           -- SELECT 查询
ROW SHARE              -- SELECT FOR UPDATE/SHARE
ROW EXCLUSIVE          -- INSERT, UPDATE, DELETE
SHARE UPDATE EXCLUSIVE -- VACUUM, CREATE INDEX CONCURRENTLY
SHARE                  -- CREATE INDEX
SHARE ROW EXCLUSIVE    -- CREATE TRIGGER
EXCLUSIVE              -- 阻止并发读写
ACCESS EXCLUSIVE       -- DROP, TRUNCATE, 大部分 ALTER TABLE
​
-- 行级锁
FOR UPDATE             -- 更新行
FOR SHARE              -- 共享锁
FOR KEY SHARE          -- 外键检查
FOR NO KEY UPDATE      -- 更新非键列

MySQL (InnoDB) 的主要锁类型:

-- 表级锁
LOCK TABLE table_name READ;      -- 共享表锁
LOCK TABLE table_name WRITE;     -- 排他表锁
​
-- 行级锁
SELECT ... FOR UPDATE;           -- 排他行锁
SELECT ... LOCK IN SHARE MODE;   -- 共享行锁
​
-- 特殊的锁机制
记录锁(Record Locks)           -- 锁住索引记录
间隙锁(Gap Locks)             -- 锁住索引范围
临键锁(Next-Key Locks)        -- 记录锁+间隙锁
插入意向锁(Insert Intention Locks)-- 插入操作专用

3. 锁实现机制的根本差异

PostgreSQL:MVCC 为主导

-- PostgreSQL 主要通过多版本控制避免锁竞争
-- 读操作通常不阻塞写操作,写操作也不阻塞读操作
-- 只有在 DDL 或特定冲突时才需要强锁
​
-- 示例:读不阻塞写
-- 会话 A
BEGIN;
SELECT * FROM accounts; -- 不阻塞其他会话的写入
​
-- 会话 B(同时执行)
UPDATE accounts SET balance = balance + 100; -- 可以正常执行

MySQL:锁机制为主导

-- MySQL 的可重复读级别严重依赖锁机制
-- 写操作会阻塞其他事务的冲突操作
​
-- 示例:Next-Key Locking 防止幻读
-- 会话 A
BEGIN;
SELECT * FROM accounts WHERE id > 100 FOR UPDATE; -- 锁住 id>100 的范围
​
-- 会话 B(被阻塞)
INSERT INTO accounts (id, balance) VALUES (150, 1000); -- 这个插入会被阻塞

4. 死锁处理方式对比

PostgreSQL 死锁处理:

-- PostgreSQL 有自动死锁检测,检测到死锁时会自动回滚其中一个事务
ERROR:  deadlock detected
DETAIL:  Process 12345 waits for ShareLock on transaction 12346; 
         blocked by process 12347.
Process 12347 waits for ShareLock on transaction 12345; 
         blocked by process 12345.
HINT:  See server log for query details.

MySQL 死锁处理:

-- MySQL 也有死锁检测,但行为略有不同
-- 默认会回滚代价较小的事务
ERROR 1213 (40001): Deadlock found when trying to get lock; 
try restarting transaction

5. 实际使用场景对比

高并发读场景:

-- PostgreSQL 表现更好(MVCC 优势)
-- 多个会话可以同时读取,写入只在冲突时等待
​
-- MySQL 在可重复读级别下,SELECT ... FOR UPDATE 会阻塞其他事务

高并发写场景:

-- MySQL 的行锁机制更精细,写冲突处理更好
-- PostgreSQL 在热点数据更新时可能出现锁竞争
​
-- 示例:计数器更新
-- PostgreSQL
UPDATE counters SET value = value + 1 WHERE id = 1; -- 行级锁,但可能成为热点
​
-- MySQL(类似的锁行为,但间隙锁可能影响范围)

6. 锁监控和诊断

PostgreSQL 锁监控:

-- 查看当前锁信息
SELECT * FROM pg_locks;
SELECT * FROM pg_stat_activity WHERE wait_event_type = 'Lock';
​
-- 详细的锁查询
SELECT 
    pg_stat_activity.pid,
    pg_stat_activity.query,
    pg_locks.mode,
    pg_locks.granted
FROM pg_stat_activity
JOIN pg_locks ON pg_stat_activity.pid = pg_locks.pid;

MySQL 锁监控:

-- 查看 InnoDB 锁信息
SHOW ENGINE INNODB STATUS;  -- 查看锁信息
SELECT * FROM information_schema.INNODB_LOCKS;  -- 当前锁
SELECT * FROM information_schema.INNODB_LOCK_WAITS;  -- 锁等待
​
-- 性能模式监控
SELECT * FROM performance_schema.data_locks;

7. 性能影响对比

场景PostgreSQLMySQL
纯读负载⭐⭐⭐⭐⭐ (MVCC 无锁)⭐⭐⭐⭐ (有少量锁开销)
读写混合⭐⭐⭐⭐ (读写不阻塞)⭐⭐⭐ (写可能阻塞读)
高并发写⭐⭐⭐ (可能锁竞争)⭐⭐⭐⭐ (行锁精细)
批量操作⭐⭐⭐⭐⭐⭐⭐ (锁开销较大)
DDL 操作⭐⭐ (ACCESS EXCLUSIVE 锁严格)⭐⭐⭐⭐ (Online DDL 支持好)

8. 总结与选择建议

选择 PostgreSQL 的情况:

选择 MySQL 的情况:

通用建议:

两种数据库的锁机制各有优势,选择取决于具体的应用场景、数据访问模式和团队的技术栈熟悉程度。

到此这篇关于PostgreSQL与MySQL的锁与隔离级别的文章就介绍到这了,更多相关mysql锁与隔离级别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文