事务的四大隔离级别、数据库中的共享锁与排他锁、MySQL 的行级锁与表级锁
关于MySQL中事务的介绍
事务的四大隔离级别及其所解决的读现象如下:
在DBMS中,事务保证了一个操作序列可以全部都执行或者全部都不执行(原子性),从一个状态转变为另一个状态(一致性)。由于事务满足持久性,所以一旦事务被提交之后,数据就能够被持久化下来,又因为事务是满足隔离性的,所以,当多个事务同时处理同一个数据的时候,多个事务直接是互不影响的,所以,在多个事务并发操作的过程中,如果控制不好隔离级别,就有可能产生脏读、不可重复读、丢失修改、或者幻读等读现象。
在数据库事务的ACID四个属性中,隔离性是一个最常放松的一个。可以在数据库操作中利用数据库的锁机制或者多版本并发控制机制来获取更高的隔离级别。但是,随着数据库隔离级别的提高,数据的并发能力也会有所下降。所以,如何在并发性和隔离性之间做一个和很好的权衡就成了一个至关重要的问题。
ANSI/ISO SQL定义的标准隔离级别有四种,从高到底依次为:可序列化(Serializable)、可重复读(Repeatable reads)、提交读(Read committed)、未提交读(Read uncommitted)。
下面将依次介绍这四种事务隔离级别的概念、用法以及解决了哪些问题(读现象)
事务的隔离级别定义了一个事务对其他事务的可见性,MySQL支持四种隔离级别,从低到高分别为:
-
读未提交(Read Uncommitted)
定义:这是最低的隔离级别,在这个级别下,一个事务可以读取到其他事务尚未提交的数据。
解决的问题:无。
存在的问题:脏读(Dirty Read)、不可重复读(Non-Repeatable Read)、幻读(Phantom Read)。
实现原理:锁机制:几乎不使用任何锁,事务可以读取其他事务尚未提交的数据。
-- transaction a start transaction; update accounts set balance = balance - 100 where user_id = 1; -- transaction b start transaction; select balance from accounts where user_id = 1; -- 可能读取到未提交的数据 commit; -- transaction a rollback;
-
读已提交(Read Committed)
定义:在这个隔离级别下,一个事务只能读取到其他事务已经提交的数据。
解决的问题:解决了脏读问题,提高了数据一致性。
存在的问题:不可重复读、幻读。
实现原理--锁机制:事务在读取数据时会使用共享锁(S锁),但在事务提交后会释放这些锁。因此,事务只能读取到其他事务已经提交的数据。
-- transaction a start transaction; update accounts set balance = balance - 100 where user_id = 1; commit; -- transaction b start transaction; select balance from accounts where user_id = 1; -- 读取到事务a提交后的数据 commit;
-
可重复读(Repeatable Read)
定义:这是MySQL的默认隔离级别。在这个级别下,一个事务在整个执行期间看到的数据是一致的,即多次读取同一数据的结果相同,即使其他事务在这期间对数据进行了修改并提交。
解决的问题:解决了脏读和不可重复读问题,提高了数据一致性。
存在的问题:幻读。
实现原理--锁机制:事务在读取数据时会使用多版本并发控制(MVCC),确保事务在这个行期间看到的数据是一致的。MySQL使用快照读(Snapshot Read)来实现这一点。
-- transaction a start transaction; select balance from accounts where user_id = 1; -- 第一次读取 update accounts set balance = balance - 100 where user_id = 1; commit; -- transaction b start transaction; select balance from accounts where user_id = 1; -- 读取到事务a开始前的数据 commit;
-
序列化(Serializable)
定义:这是最高的隔离级别,通过强制事务串行执行来避免并发问题。在这个级别下,事务完全按照顺序执行,不会出现并发操作。
解决的问题:解决了所有读现象问题,包括脏读、不可重复读和幻读。
存在的问题:性能较低,因为事务需要排队执行,可能会导致系统吞吐量下降。
实现原理--锁机制:事务通过强制事务串行执行来避免并发问题。MySQL使用表级锁或行级锁来实现这一点,确保事务完全按照顺序执行。
-- transaction a start transaction; select * from accounts where user_id = 1 for update; -- 获取排他锁 update accounts set balance = balance - 100 where user_id = 1; commit; -- transaction b start transaction; select * from accounts where user_id = 1 for share; -- 需要等待事务a释放排他锁 commit;
读现象解释:
-
脏读(Dirty Read):一个事务读取了另一个事务未提交的数据
-
不可重复度(Non-Repeatable Read:一个事务在两次查询之间,另一个事务对数据进行了修改并提交,导致同一个事务在不同时间读取统一数据得到了不同的结果。
-
幻读:(Phantom Read):一个事务在两次查询之间,另一个事务插入了新的数据行,导致第一个事务在第二次查询时看到了之前不存在的数据行。
数据库中的共享锁与排他锁
在数据库中,锁机制是确保数据一致性和并发控制的重要手段。共享锁(Shared Lock,简称S锁)和排他锁(Exclusive Lock,简称X锁)是最基本的两种锁类型。下面详细介绍这两种锁的特性、用途以及它们之间的关系。
共享锁(Shared Lock,S锁)
定义:共享锁也称为读锁,允许多个事务同时读取同一数据项,但不允许任何事务修改该数据项。
特性:
-
共享性:多个事务可以同时持有同一数据项的共享锁。
-
互斥性:如果一个事务持有一个数据项的共享锁,其他事务不能对该数据项加排他锁。
用途:
-
读操作:主要用于读取数据,确保读取过程中数据不会被其他事务修改。
示例:
-- 事务A
START TRANSACTION;
SELECT * FROM accounts WHERE user_id = 1 FOR SHARE; -- 获取共享锁
COMMIT;
-- 事务B
START TRANSACTION;
SELECT * FROM accounts WHERE user_id = 1 FOR SHARE; -- 可以获取共享锁
COMMIT;
排他锁(Exclusive Lock,X锁)
定义:排他锁也称为写锁,允许一个事务独占性地访问某一数据项,禁止其他事务读取或修改该数据项。
特性:
-
独占性:在同一时间,只有一个事务可以持有某一数据项的排他锁。
-
互斥性:如果一个事务持有一个数据项的排他锁,其他事务不能对该数据项加任何类型的锁(包括共享锁和排他锁)。
用途:
-
写操作:主要用于修改数据,确保修改过程中数据不会被其他事务读取或修改。
示例:
-- 事务A
START TRANSACTION;
SELECT * FROM accounts WHERE user_id = 1 FOR UPDATE; -- 获取排他锁
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
COMMIT;
-- 事务B
START TRANSACTION;
SELECT * FROM accounts WHERE user_id = 1 FOR SHARE; -- 需要等待事务A释放排他锁
COMMIT;
共享锁与排他锁的关系
-
兼容性:
-
共享锁与共享锁:兼容,多个事务可以同时持有同一数据项的共享锁。
-
共享锁与排他锁:不兼容,如果一个事务持有一个数据项的共享锁,其他事务不能对该数据项加排他锁。
-
排他锁与排他锁:不兼容,如果一个事务持有一个数据项的排他锁,其他事务不能对该数据项加任何类型的锁。
-
-
锁转换:
-
共享锁转排他锁:通常不允许直接将共享锁转换为排他锁,需要先释放共享锁,再加排他锁。
-
排他锁转共享锁:可以直接将排他锁降级为共享锁,但通常不需要这样做,因为排他锁已经确保了独占性。
-
MySQL 的行级锁与表级锁
在 MySQL 中,锁机制是保证并发控制和数据一致性的关键。根据锁的作用范围不同,主要分为行级锁和表级锁。
表级锁
-
定义:表级锁是 MySQL 中最基本的锁策略,也是最粗粒度的锁。当一个事务对某个表进行操作时,会锁定整个表,其他事务不能对该表进行任何修改操作。
-
优点:
-
实现简单,开销小。
-
资源消耗少,加锁快。
-
-
缺点:
-
锁粒度大,容易引发锁争用,导致并发性能下降。
-
-
适用场景:
-
适用于读多写少的场景,或者对数据一致性要求不高的场景。
-
-
示例:
-
MyISAM
存储引擎使用表级锁。 -
InnoDB
存储引擎在某些情况下也会使用表级锁,例如在执行LOCK TABLES
语句时。
-- MyISAM 存储引擎 CREATE TABLE myisam_table ( id INT PRIMARY KEY, name VARCHAR(50) ) ENGINE=MyISAM; -- 插入数据 INSERT INTO myisam_table (id, name) VALUES (1, 'Alice'), (2, 'Bob'); -- 事务1 START TRANSACTION; UPDATE myisam_table SET name = 'Charlie' WHERE id = 1; -- 事务2 START TRANSACTION; UPDATE myisam_table SET name = 'David' WHERE id = 2; -- 会被阻塞,直到事务1提交或回滚
-
行级锁
-
定义:行级锁是 MySQL 中最细粒度的锁,只锁定需要操作的行。行级锁可以最大限度地支持并发操作,但实现复杂,开销较大。
-
优点:
-
锁粒度小,能够支持高并发。
-
减少了锁冲突的概率,提高了系统的并发性能。
-
-
缺点:
-
实现复杂,开销较大。
-
可能会导致死锁问题。
-
-
适用场景:
-
适用于写多读少的场景,或者对数据一致性要求较高的场景。
-
-
示例:
-
InnoDB
存储引擎默认使用行级锁。 -
在
InnoDB
中,可以通过SELECT ... FOR UPDATE
和SELECT ... LOCK IN SHARE MODE
来显式地获取行级锁。
-- InnoDB 存储引擎 CREATE TABLE innodb_table ( id INT PRIMARY KEY, name VARCHAR(50) ) ENGINE=InnoDB; -- 插入数据 INSERT INTO innodb_table (id, name) VALUES (1, 'Alice'), (2, 'Bob'); -- 事务1 START TRANSACTION; SELECT * FROM innodb_table WHERE id = 1 FOR UPDATE; -- 事务2 START TRANSACTION; SELECT * FROM innodb_table WHERE id = 2 FOR UPDATE; -- 不会被阻塞,因为锁定的是不同的行
-
行级锁和表级锁对比
死锁及其解决方法
-
定义:死锁是指两个或多个事务在等待对方释放锁,从而导致所有事务都无法继续执行的情况。
-
检测:
-
MySQL 会定期检测死锁,并选择一个事务进行回滚以解除死锁。
-
-
预防:
-
尽量减少事务的持有锁时间。
-
按照固定的顺序访问资源。
-
使用超时机制,避免长时间等待。
-
-
解决:
-
使用
SHOW ENGINE INNODB STATUS
查看当前的死锁信息。 -
分析日志,找出死锁的原因并优化事务逻辑。
-