让网站排名下降,e4a怎么做点击跳转网站,西安企业自助建站系统,wordpress 显示字体大小1. 什么是数据库事务
定义#xff1a;事务#xff08;Transaction#xff09;是数据库管理系统中的一个逻辑工作单元#xff0c;用于确保一组相关操作要么全部成功执行#xff0c;要么全部不执行#xff0c;从而维护数据的一致性和完整性。重要性#xff1a;在多用户环…1. 什么是数据库事务
定义事务Transaction是数据库管理系统中的一个逻辑工作单元用于确保一组相关操作要么全部成功执行要么全部不执行从而维护数据的一致性和完整性。重要性在多用户环境下当多个事务并发执行时为了保证数据的完整性和一致性事务的概念变得至关重要。例如在银行转账系统中从一个账户扣款并给另一个账户加款这两个操作必须同时成功或者同时失败否则就会导致资金账目混乱。
2. 事务四个特性ACID特性
2.1. 原子性Atomicity
2.1.1. 定义
原子性保证事务是一个不可分割的工作单元其中的操作要么全部完成要么全部不执行。如果事务中的任何部分失败则整个事务将被撤销。
2.1.2. 实现原理
日志记录Logging数据库系统在执行事务时会将所有操作记录到一个日志文件中。这些日志条目包括事务的开始、每个操作及其参数、以及事务的结束。两阶段提交Two-Phase Commit, 2PC用于分布式系统中的事务管理确保所有参与节点要么全部成功提交要么全部回滚。回滚机制Rollback Mechanism如果事务中的某个操作失败系统会根据日志中的信息撤销之前的所有操作恢复到事务开始前的状态。
2.1.3. 示例
假设有一个事务包含三个操作INSERT INTO users (name, age) VALUES (‘Alice’, 25)、UPDATE accounts SET balance balance 100 WHERE user_id 1 和 DELETE FROM logs WHERE user_id 1。如果在执行第三个操作时发生了错误那么数据库会根据日志将前两个操作撤销使数据库状态回到事务开始之前就像这个事务从未执行过一样。
2.2. 一致性Consistency
2.2.1. 定义
一致性确保事务必须使数据库从一个一致状态转换到另一个一致状态。事务执行前后数据库的完整性约束没有被破坏。
2.2.2. 实现原理
约束检查Constraint Checking数据库系统在事务执行过程中会检查并验证所有相关的完整性约束如主键、外键、唯一性约束等以确保数据始终处于一致状态。触发器Triggers可以定义触发器来自动执行某些操作以维护数据的一致性。例如在插入或更新数据时自动更新相关表的数据。事务验证Transaction Validation在事务提交前系统会验证事务是否满足所有的业务规则和完整性约束。
2.2.3. 示例
在一个学生成绩管理系统中规定学生的成绩范围为0 - 100分。如果一个事务试图将某个学生的成绩更新为120分那么该事务将违反域完整性约束DBMS会拒绝执行此操作以确保数据的一致性。
2.3. 隔离性Isolation
2.3.1. 定义
多个事务并发执行时一个事务的执行不应影响其他事务的执行。每个事务都应在独立的环境中运行就像它是唯一正在运行的事务一样。
2.3.2. 实现原理
锁机制Locking通过加锁来防止多个事务同时访问和修改同一数据项。常见的锁类型包括行级锁、表级锁、读锁和写锁。多版本并发控制Multi-Version Concurrency Control, MVCC为每个事务提供数据的不同版本视图允许多个事务并发读取而不互相干扰。MVCC通过保存旧版本的数据快照来实现这一点。隔离级别Isolation Levels数据库系统提供了不同的隔离级别允许用户根据需求选择合适的隔离程度。常见的隔离级别包括 读未提交Read Uncommitted最低隔离级别允许脏读。读已提交Read Committed不允许脏读但允许不可重复读。可重复读Repeatable Read不允许脏读和不可重复读但允许幻读。串行化Serializable最高隔离级别完全避免了脏读、不可重复读和幻读。
2.4. 持久性Durability
2.4.1. 定义
持久性确保一旦事务提交它对数据库所做的更改将是永久性的即使系统发生故障也不会丢失。
2.4.2. 实现原理
日志持久化Log Persistence所有事务的操作都会先记录到日志文件中并且在事务提交后立即将日志刷入磁盘。即使系统崩溃也可以通过重做日志恢复数据。检查点Checkpointing定期创建检查点将内存中的数据同步到磁盘减少恢复时间。双重缓冲Double Buffering使用双重缓冲技术确保数据在写入磁盘时不会丢失。
2.4.3. 示例
在一个在线支付系统中当用户完成一笔支付后涉及到更新用户的余额信息等操作。假设在事务提交后服务器突然断电由于采用了持久性机制数据库可以根据日志恢复支付操作确保用户的余额信息不会丢失支付业务能够正常完成。
3. 数据库事务操作
在数据库管理系统中事务可以分为显式事务Explicit Transactions和隐式事务Implicit Transactions。
3.1. 显式事务Explicit Transactions
1. 定义显式事务是由用户显式地通过SQL语句开始和结束的事务。用户明确地使用 BEGIN TRANSACTION、COMMIT 和 ROLLBACK 等语句来控制事务的边界。
2. 特点
用户控制开发者明确地定义事务的开始和结束。灵活性可以包含任意数量的操作并且可以根据需要进行回滚。性能优化可以更好地控制事务的粒度减少锁的持有时间提高并发性能。
3. SQL语句
开始事务BEGIN TRANSACTION 或 START TRANSACTION提交事务COMMIT回滚事务ROLLBACK
4. 示例
-- 开始事务
START TRANSACTION;-- 执行SQL操作
INSERT INTO account (id, balance) VALUES (1, 1000);
UPDATE account SET balance balance - 100 WHERE id 1;
INSERT INTO transaction_log (account_id, amount) VALUES (1, -100);-- 提交事务
COMMIT;-- 或者回滚事务
-- ROLLBACK;3.2. 隐式事务Implicit Transactions
1. 定义隐式事务是由数据库自动管理的事务。每个单独的SQL语句被视为一个独立的事务自动提交或回滚。
2. 特点
自动管理数据库自动处理事务的开始和结束。简单性对于简单的操作隐式事务简化了事务管理。限制性每个SQL语句都是一个独立的事务无法包含多个操作。
3. SQL语句
自动提交每个SQL语句执行后自动提交。自动回滚某些错误可能导致SQL语句自动回滚。
4. 示例
-- 插入操作自动提交
INSERT INTO account (id, balance) VALUES (1, 1000);-- 更新操作自动提交
UPDATE account SET balance balance - 100 WHERE id 1;-- 插入操作自动提交
INSERT INTO transaction_log (account_id, amount) VALUES (1, -100);3.3. 显式事务和隐式事务的比较
特性显式事务 (Explicit Transactions)隐式事务 (Implicit Transactions)控制方式用户显式控制事务的开始和结束数据库自动管理事务的开始和结束事务边界开发者定义事务的开始和结束点每个SQL语句被视为一个独立的事务灵活性可以包含多个操作根据需要进行回滚每个操作都是独立的无法包含多个操作性能可以更好地控制事务的粒度减少锁的持有时间提高并发性能每个操作自动提交可能增加锁的持有时间影响并发性能适用场景复杂的事务操作需要确保多个操作的原子性简单的操作每个操作独立不需要复杂的事务控制
3.4. Savepoint
1. 定义Savepoint 是事务中的一个标记点允许用户在事务中设置一个保存点并在需要时回滚到该保存点而不影响事务中其他部分的操作。这为事务提供了更灵活的回滚机制。
2. 特点
细粒度回滚可以回滚到事务中的特定点而不是整个事务。提高灵活性允许在事务中进行部分回滚而不影响其他操作。性能优化减少不必要的回滚操作提高事务处理效率。
3. SQL语句
设置保存点SAVEPOINT savepoint_name回滚到保存点ROLLBACK TO SAVEPOINT savepoint_name释放保存点RELEASE SAVEPOINT savepoint_name
4. 示例
-- 开始事务
START TRANSACTION;-- 执行一些操作
INSERT INTO account (id, balance) VALUES (1, 1000);-- 设置保存点
SAVEPOINT savepoint1;-- 执行更多操作
UPDATE account SET balance balance - 100 WHERE id 1;-- 设置另一个保存点
SAVEPOINT savepoint2;-- 执行更多操作
INSERT INTO transaction_log (account_id, amount) VALUES (1, -100);-- 回滚到 savepoint2
ROLLBACK TO SAVEPOINT savepoint2;-- 释放 savepoint1
RELEASE SAVEPOINT savepoint1;-- 提交事务
COMMIT;详细步骤
1开始事务START TRANSACTION;
2插入操作INSERT INTO account (id, balance) VALUES (1, 1000);
3设置保存点 savepoint1SAVEPOINT savepoint1;
4更新操作UPDATE account SET balance balance - 100 WHERE id 1;
5设置保存点 savepoint2SAVEPOINT savepoint2;
6插入操作INSERT INTO transaction_log (account_id, amount) VALUES (1, -100);
7回滚到 savepoint2ROLLBACK TO SAVEPOINT savepoint2;注意这将撤销插入到 transaction_log 的操作但保留 savepoint1 之前的更新操作。
8释放 savepoint1RELEASE SAVEPOINT savepoint1;
9提交事务COMMIT;
代码示例Java JDBC
import java.sql.*;public class SavepointExample {public static void main(String[] args) {String url jdbc:mysql://localhost:3306/test;String user root;String password root;try (Connection conn DriverManager.getConnection(url, user, password)) {// 关闭自动提交模式conn.setAutoCommit(false);try {// 开始事务conn.setAutoCommit(false);// 执行一些操作Statement stmt conn.createStatement();stmt.executeUpdate(INSERT INTO account (id, balance) VALUES (1, 1000));// 设置保存点Savepoint savepoint1 conn.setSavepoint(savepoint1);// 执行更多操作stmt.executeUpdate(UPDATE account SET balance balance - 100 WHERE id 1);// 设置另一个保存点Savepoint savepoint2 conn.setSavepoint(savepoint2);// 执行更多操作stmt.executeUpdate(INSERT INTO transaction_log (account_id, amount) VALUES (1, -100));// 回滚到 savepoint2conn.rollback(savepoint2);// 释放 savepoint1conn.releaseSavepoint(savepoint1);// 提交事务conn.commit();System.out.println(事务提交成功);} catch (SQLException e) {// 回滚事务conn.rollback();System.out.println(事务回滚);e.printStackTrace();} finally {// 恢复自动提交模式conn.setAutoCommit(true);}} catch (SQLException e) {e.printStackTrace();}}
}3.5. 只读事务
1. 定义只读事务是指在事务期间不允许对数据库进行任何修改操作。只读事务可以提高并发性能因为数据库管理系统可以对只读操作进行优化减少锁的使用。
2. 特点
提高并发性能只读事务不会修改数据减少了锁的争用。数据一致性确保事务期间数据不会被其他事务修改。简化事务管理只读事务不需要回滚操作简化了事务管理。
3. SQL语句
设置只读事务在某些数据库中可以通过特定的语法设置只读事务。例如在某些数据库中可以使用 SET TRANSACTION READ ONLY。
4. 示例
-- 开始只读事务
SET TRANSACTION READ ONLY;
START TRANSACTION;-- 执行只读操作
SELECT * FROM account WHERE id 1;-- 提交事务
COMMIT;详细步骤
1设置只读事务SET TRANSACTION READ ONLY;
2开始事务START TRANSACTION;
3执行只读操作SELECT * FROM account WHERE id 1;
4提交事务COMMIT;
代码示例Java JDBC
import java.sql.*;public class ReadOnlyTransactionExample {public static void main(String[] args) {String url jdbc:mysql://localhost:3306/test;String user root;String password root;try (Connection conn DriverManager.getConnection(url, user, password)) {// 关闭自动提交模式conn.setAutoCommit(false);try {// 设置只读事务conn.setReadOnly(true);// 开始事务conn.setAutoCommit(false);// 执行只读操作Statement stmt conn.createStatement();ResultSet rs stmt.executeQuery(SELECT * FROM account WHERE id 1);while (rs.next()) {int id rs.getInt(id);int balance rs.getInt(balance);System.out.println(id id , balance balance);}// 提交事务conn.commit();System.out.println(事务提交成功);} catch (SQLException e) {// 回滚事务conn.rollback();System.out.println(事务回滚);e.printStackTrace();} finally {// 恢复自动提交模式conn.setAutoCommit(true);// 恢复可写模式conn.setReadOnly(false);}} catch (SQLException e) {e.printStackTrace();}}
}4. 数据库隔离级别
数据库隔离级别定义了事务之间如何隔离以确保数据的一致性和完整性。SQL标准定义了四个隔离级别每个级别都提供了不同程度的隔离性同时也带来了不同的并发性能和数据一致性问题。数据库隔离级别从低到高依次为读未提交Read Uncommitted、读已提交Read Committed、可重复读Repeatable Read、串行化Serializable。以下是详细的隔离级别及其产生的问题。
4.1. 读未提交Read Uncommitted
4.1.1. 定义
事务可以读取其他事务未提交的数据。
4.1.2. 特点
最低隔离级别允许事务读取未提交的数据。并发性能最高由于不需要等待其他事务提交可以立即读取数据。
4.1.3. 产生的问题
脏读Dirty Read事务读取了其他事务未提交的数据如果其他事务回滚读取的数据将不一致。 脏读示例: 1. 数据库初始状态 假设有一个表 account包含以下数据 idbalance11000.00 2. 流程图 事务A事务BBEGIN;UPDATE account SET balance 500 WHERE id 1;BEGIN; SELECT balance FROM account WHERE id 1;SELECT balance FROM account WHERE id 1;ROLLBACK; 3. 详细步骤 1事务A开始并更新数据 事务A 开始执行并更新 account 表中 id 1 的 balance 为 500。 -- 事务A
BEGIN;
UPDATE account SET balance 500 WHERE id 1;2事务B开始并读取数据 事务B 开始执行并读取 account 表中 id 1 的 balance。 -- 事务B
BEGIN;
SELECT balance FROM account WHERE id 1;事务B 读取到的 balance 是 500这是事务A未提交的数据。 3事务A回滚 事务A 回滚撤销之前的操作将 balance 恢复为 1000。 -- 事务A
ROLLBACK;4事务B再次读取数据 事务B 再次读取 account 表中 id 1 的 balance。 -- 事务B
SELECT balance FROM account WHERE id 1;事务B 读取到的 balance 是 1000与之前读取到的 500 不一致。 4. 脏读问题分析 初始状态balance 为 1000。事务A更新数据将 balance 更新为 500但未提交。事务B读取数据读取到事务A未提交的 balance 值 500。事务A回滚balance 恢复为 1000。事务B再次读取数据读取到 balance 为 1000与之前读取到的 500 不一致。 5. 代码示例 以下是使用Java和JDBC的代码示例展示脏读的问题 import java.sql.*;public class DirtyReadExample {public static void main(String[] args) {String url jdbc:mysql://localhost:3306/test;String user root;String password root;try (Connection connA DriverManager.getConnection(url, user, password);Connection connB DriverManager.getConnection(url, user, password)) {// 设置事务隔离级别为读未提交connA.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);connB.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);// 事务A开始并更新数据connA.setAutoCommit(false);Statement stmtA connA.createStatement();stmtA.executeUpdate(UPDATE account SET balance 500 WHERE id 1);System.out.println(事务A更新数据balance 500);// 事务B开始并读取数据connB.setAutoCommit(false);Statement stmtB connB.createStatement();ResultSet rsB stmtB.executeQuery(SELECT balance FROM account WHERE id 1);if (rsB.next()) {int balance rsB.getInt(balance);System.out.println(事务B读取数据balance balance);}// 事务A回滚connA.rollback();System.out.println(事务A回滚);// 事务B再次读取数据rsB stmtB.executeQuery(SELECT balance FROM account WHERE id 1);if (rsB.next()) {int balance rsB.getInt(balance);System.out.println(事务B再次读取数据balance balance);}// 提交事务BconnB.commit();} catch (SQLException e) {e.printStackTrace();}}
}输出结果 事务A更新数据balance 500
事务B读取数据balance 500
事务A回滚
事务B再次读取数据balance 10006. 总结 通过上述流程和代码示例可以看出脏读的问题在于事务B读取了事务A未提交的数据而事务A最终回滚导致事务B读取的数据不一致。选择合适的隔离级别如读已提交或更高可以避免脏读问题。 4.2. 读已提交Read Committed
4.2.1. 定义
事务只能读取其他事务已经提交的数据。
4.2.2. 特点
避免脏读事务只能读取已提交的数据。并发性能较高允许事务在其他事务提交后读取数据。
4.2.3. 产生的问题
不可重复读Non-repeatable Read事务在同一个查询中多次读取同一数据时数据可能被其他事务修改导致结果不一致。 不可重复读示例: 1. 数据库初始状态 假设有一个表 account包含以下数据 idbalance11000.00 2. 流程图 事务A事务BBEGIN; SELECT balance FROM account WHERE id 1;BEGIN; UPDATE account SET balance 500 WHERE id 1; COMMIT;SELECT balance FROM account WHERE id 1; 3. 详细步骤 1事务A开始并读取数据 事务A 开始执行并读取 account 表中 id 1 的 balance。 -- 事务A
BEGIN;
SELECT balance FROM account WHERE id 1;事务A 读取到的 balance 是 1000。 2事务B开始并更新数据 事务B 开始执行并更新 account 表中 id 1 的 balance 为 500。 -- 事务B
BEGIN;
UPDATE account SET balance 500 WHERE id 1;
COMMIT;3事务A再次读取数据 事务A 再次读取 account 表中 id 1 的 balance。 -- 事务A
SELECT balance FROM account WHERE id 1;事务A 读取到的 balance 是 500与之前读取到的 1000 不一致。 4. 不可重复读问题分析 初始状态balance 为 1000。事务A读取数据读取到 balance 为 1000。事务B更新数据将 balance 更新为 500 并提交。事务A再次读取数据读取到 balance 为 500与之前读取到的 1000 不一致。 5. 代码示例 import java.sql.*;public class NonRepeatableReadExample {public static void main(String[] args) {String url jdbc:mysql://localhost:3306/test;String user root;String password root;try (Connection connA DriverManager.getConnection(url, user, password);Connection connB DriverManager.getConnection(url, user, password)) {// 设置事务隔离级别为读已提交connA.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);connB.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);// 事务A开始并读取数据connA.setAutoCommit(false);Statement stmtA connA.createStatement();ResultSet rsA stmtA.executeQuery(SELECT balance FROM account WHERE id 1);if (rsA.next()) {int balance rsA.getInt(balance);System.out.println(事务A第一次读取数据balance balance);}// 事务B开始并更新数据connB.setAutoCommit(false);Statement stmtB connB.createStatement();stmtB.executeUpdate(UPDATE account SET balance 500 WHERE id 1);connB.commit();System.out.println(事务B更新数据balance 500);// 事务A再次读取数据rsA stmtA.executeQuery(SELECT balance FROM account WHERE id 1);if (rsA.next()) {int balance rsA.getInt(balance);System.out.println(事务A第二次读取数据balance balance);}// 提交事务AconnA.commit();} catch (SQLException e) {e.printStackTrace();}}
}输出结果 事务A第一次读取数据balance 1000
事务B更新数据balance 500
事务A第二次读取数据balance 5006. 总结 通过上述流程和代码示例可以看出不可重复读的问题在于事务A在同一个查询中多次读取同一数据时数据被事务B修改导致结果不一致。选择合适的隔离级别如可重复读或更高可以避免不可重复读问题。 4.3. 可重复读Repeatable Read
4.3.1. 定义
事务在同一个查询中多次读取同一数据时数据保持一致即使其他事务修改了这些数据。
4.3.2. 特点
避免脏读和不可重复读事务在读取数据后其他事务对该数据的修改不会影响当前事务的后续读取。并发性能适中通过锁或多版本并发控制MVCC实现。
4.3.3. 产生的问题
幻读Phantom Read事务在同一个查询中多次读取同一范围的数据时数据集可能被其他事务插入或删除导致结果不一致。 幻读示例: 1. 数据库初始状态 假设有一个表 account包含以下数据 idbalance11000.0022000.00 2. 流程图 事务A事务BBEGIN; SELECT * FROM account WHERE balance 1500;BEGIN; INSERT INTO account (id, balance) VALUES (3, 3000); COMMIT;SELECT * FROM account WHERE balance 1500; 3. 详细步骤 1事务A开始并读取数据 事务A 开始执行并读取 account 表中 balance 1500 的所有记录。 -- 事务A
BEGIN;
SELECT * FROM account WHERE balance 1500;事务A 读取到的记录是 idbalance22000.00 2事务B开始并插入数据 事务B 开始执行并插入一条新的记录 id 3balance 3000。 -- 事务B
BEGIN;
INSERT INTO account (id, balance) VALUES (3, 3000);
COMMIT;3事务A再次读取数据 事务A 再次读取 account 表中 balance 1500 的所有记录。 -- 事务A
SELECT * FROM account WHERE balance 1500;事务A 读取到的记录是 idbalance22000.0033000.00 4. 幻读问题分析 初始状态表中有两条记录id 1 和 id 2balance 分别为 1000 和 2000。事务A读取数据读取到 balance 1500 的记录即 id 2balance 2000。事务B插入数据插入一条新的记录 id 3balance 3000。事务A再次读取数据读取到 balance 1500 的记录包括 id 2 和 id 3出现了新的记录 id 3即幻读。 5. 代码示例 import java.sql.*;public class PhantomReadExample {public static void main(String[] args) {String url jdbc:mysql://localhost:3306/test;String user root;String password root;try (Connection connA DriverManager.getConnection(url, user, password);Connection connB DriverManager.getConnection(url, user, password)) {// 设置事务隔离级别为可重复读connA.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);connB.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);// 事务A开始并读取数据connA.setAutoCommit(false);Statement stmtA connA.createStatement();ResultSet rsA stmtA.executeQuery(SELECT * FROM account WHERE balance 1500);System.out.println(事务A第一次读取数据);while (rsA.next()) {int id rsA.getInt(id);int balance rsA.getInt(balance);System.out.println(id id , balance balance);}// 事务B开始并插入数据connB.setAutoCommit(false);Statement stmtB connB.createStatement();stmtB.executeUpdate(INSERT INTO account (id, balance) VALUES (3, 3000));connB.commit();System.out.println(事务B插入数据id 3, balance 3000);// 事务A再次读取数据rsA stmtA.executeQuery(SELECT * FROM account WHERE balance 1500);System.out.println(事务A第二次读取数据);while (rsA.next()) {int id rsA.getInt(id);int balance rsA.getInt(balance);System.out.println(id id , balance balance);}// 提交事务AconnA.commit();} catch (SQLException e) {e.printStackTrace();}}
}输出结果 事务A第一次读取数据
id 2, balance 2000
事务B插入数据id 3, balance 3000
事务A第二次读取数据
id 2, balance 2000
id 3, balance 30006. 总结 通过上述流程和代码示例可以看出幻读的问题在于事务A在同一个查询中多次读取同一范围的数据时数据集被事务B插入了新的记录导致结果不一致。选择合适的隔离级别如串行化可以避免幻读问题。 4.4. 串行化Serializable
4.4.1. 定义
事务完全串行化执行即一个事务在另一个事务提交之前必须等待。
4.4.2. 特点
避免脏读、不可重复读和幻读事务完全隔离效果等同于顺序执行。并发性能最低事务之间完全串行化导致并发性能较差。
4.4.3. 产生的问题
性能下降由于事务之间完全隔离系统并发性能较低。
4.4.4. 示例
-- 事务A
BEGIN;
SELECT * FROM table_name WHERE column1 10; -- 第一次读取-- 事务B
BEGIN;
INSERT INTO table_name (column1) VALUES (15);
-- 事务B必须等待事务A提交或回滚-- 事务A
SELECT * FROM table_name WHERE column1 10; -- 第二次读取结果一致
COMMIT;4.5. 不同隔离级别的比较
为了更好地理解不同隔离级别如何解决这些问题以下是各个隔离级别的比较
隔离级别脏读Dirty Read不可重复读Non-repeatable Read幻读Phantom Read读未提交允许允许允许读已提交禁止允许允许可重复读禁止禁止允许串行化禁止禁止禁止
选择合适的隔离级别可以根据具体的应用需求来平衡并发性能和数据一致性。
5. 锁类型
5.1. 排他锁X锁
定义排他锁Exclusive Lock, X锁用于确保只有一个事务可以修改数据项。其他事务在此期间无法对该数据项加任何类型的锁。用途用于写操作如插入、更新和删除。示例
-- 加共享锁
SELECT * FROM table_name WHERE id 1 LOCK IN SHARE MODE;5.2. 共享锁S锁
定义共享锁Shared Lock, S锁允许多个事务同时读取同一数据项但不允许其他事务对该数据项加排他锁。用途用于读操作如查询。示例
-- 加排他锁
SELECT * FROM table_name WHERE id 1 FOR UPDATE;5.3. 锁的相容性矩阵
请求锁 \ 当前锁无锁S锁X锁无锁是是是S锁是是否X锁是否否
6. 死锁
6.1. 定义
**死锁Deadlock**是指两个或多个事务互相等待对方持有的资源如锁从而导致所有这些事务都无法继续执行的状态。在数据库系统中死锁通常发生在多个事务竞争共享资源时每个事务都持有某些资源并等待其他事务释放它们所需的资源。
6.2. 死锁的形成条件
根据Coffman条件死锁的发生需要满足以下四个必要条件
互斥条件Mutual Exclusion资源一次只能被一个事务占用不能同时被多个事务共享。占有并等待条件Hold and Wait一个事务已经持有了某些资源并且正在等待获取其他事务持有的资源。不可剥夺条件No Preemption资源不能被强制剥夺只有持有资源的事务可以主动释放资源。循环等待条件Circular Wait存在一个事务等待环即事务T1等待事务T2持有的资源事务T2等待事务T3持有的资源……事务Tn等待事务T1持有的资源。
6.3. 示例
假设有一个简单的银行转账系统包含两个账户A和B。有两个事务T1和T2分别进行转账操作
事务操作T1BEGIN; LOCK TABLE account A IN EXCLUSIVE MODE; – 获取账户A的排他锁。 UPDATE account SET balance balance - 100 WHERE id A;T2BEGIN; LOCK TABLE account B IN EXCLUSIVE MODE; – 获取账户B的排他锁。 UPDATE account SET balance balance 100 WHERE id B;T1LOCK TABLE account B IN EXCLUSIVE MODE;T2LOCK TABLE account A IN EXCLUSIVE MODE; #mermaid-svg-oAzWJdrFBlWfkYYo {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-oAzWJdrFBlWfkYYo .error-icon{fill:#552222;}#mermaid-svg-oAzWJdrFBlWfkYYo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oAzWJdrFBlWfkYYo .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-oAzWJdrFBlWfkYYo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oAzWJdrFBlWfkYYo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oAzWJdrFBlWfkYYo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oAzWJdrFBlWfkYYo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oAzWJdrFBlWfkYYo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oAzWJdrFBlWfkYYo .marker.cross{stroke:#333333;}#mermaid-svg-oAzWJdrFBlWfkYYo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oAzWJdrFBlWfkYYo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oAzWJdrFBlWfkYYo .cluster-label text{fill:#333;}#mermaid-svg-oAzWJdrFBlWfkYYo .cluster-label span{color:#333;}#mermaid-svg-oAzWJdrFBlWfkYYo .label text,#mermaid-svg-oAzWJdrFBlWfkYYo span{fill:#333;color:#333;}#mermaid-svg-oAzWJdrFBlWfkYYo .node rect,#mermaid-svg-oAzWJdrFBlWfkYYo .node circle,#mermaid-svg-oAzWJdrFBlWfkYYo .node ellipse,#mermaid-svg-oAzWJdrFBlWfkYYo .node polygon,#mermaid-svg-oAzWJdrFBlWfkYYo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oAzWJdrFBlWfkYYo .node .label{text-align:center;}#mermaid-svg-oAzWJdrFBlWfkYYo .node.clickable{cursor:pointer;}#mermaid-svg-oAzWJdrFBlWfkYYo .arrowheadPath{fill:#333333;}#mermaid-svg-oAzWJdrFBlWfkYYo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oAzWJdrFBlWfkYYo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oAzWJdrFBlWfkYYo .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-oAzWJdrFBlWfkYYo .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-oAzWJdrFBlWfkYYo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oAzWJdrFBlWfkYYo .cluster text{fill:#333;}#mermaid-svg-oAzWJdrFBlWfkYYo .cluster span{color:#333;}#mermaid-svg-oAzWJdrFBlWfkYYo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oAzWJdrFBlWfkYYo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 死锁 等待账户B的锁 等待账户A的锁 事务A 等待账户B的锁 事务B 等待账户A的锁 事务B 尝试获取账户A的排他锁 事务A 等待 事务A 尝试获取账户B的排他锁 事务B 等待 事务A 开始 事务A 获取账户A的排他锁 事务A 更新账户A 事务B 开始 事务B 获取账户B的排他锁 事务B 更新账户B 在这个例子中
T1先锁住了账户A然后尝试锁住账户B。T2先锁住了账户B然后尝试锁住账户A。
结果是T1等待T2释放账户B的锁而T2等待T1释放账户A的锁形成了死锁。
6.4. 死锁的检测与预防
6.4.1. 死锁检测
死锁检测是指通过定期检查系统状态来发现是否存在死锁。常见的检测方法包括
超时法Timeout Method如果一个事务等待某个资源的时间超过了预设的阈值则认为该事务可能陷入了死锁系统会终止该事务。等待图法Wait-for Graph构建一个有向图节点表示事务边表示事务之间的等待关系。如果图中存在环则说明存在死锁。
6.4.2. 死锁预防
死锁预防是指通过限制事务的行为来避免死锁的发生。常见的预防策略包括
一次性加锁All-or-Nothing Locking事务在开始时一次性请求所有需要的锁而不是逐步请求。这样可以避免部分加锁后等待其他锁的情况。顺序加锁Lock Ordering规定所有事务必须按照某种全局顺序加锁。例如所有事务必须先锁账户A再锁账户B这样可以避免循环等待。超时机制Timeout Mechanism设置一个合理的超时时间当事务等待资源的时间超过这个时间时自动回滚该事务以避免长时间等待导致死锁。
6.4.3. 死锁解除
一旦检测到死锁系统需要采取措施解除死锁。常见的解除方法包括
回滚事务Rollback Transactions选择一个或多个事务进行回滚释放其持有的锁使其他事务能够继续执行。通常会选择代价最小的事务进行回滚。优先级调度Priority Scheduling为事务分配优先级优先处理高优先级的事务低优先级的事务可能会被回滚以解除死锁。
6.5. 死锁的处理策略
超时法:
优点实现简单不需要复杂的图结构。缺点可能会误判正常的长等待为死锁导致不必要的事务回滚。
等待图法:
优点能够准确检测死锁适用于大多数并发控制场景。缺点需要额外的开销来维护等待图增加了系统的复杂性。
预防策略
优点从根本上避免死锁的发生减少了死锁检测和解除的开销。缺点可能会影响系统的灵活性和性能因为需要对事务的行为进行严格限制。
回滚策略
优点能够在检测到死锁后快速解决问题保证系统的正常运行。缺点回滚事务可能导致数据不一致或业务逻辑失败需要额外的补偿机制。
6.6. 死锁的处理策略
在数据库系统中死锁是一个常见的问题特别是在高并发环境下。为了应对死锁数据库管理系统通常会采用以下几种方式
自动检测和解除大多数现代数据库系统如MySQL、PostgreSQL、Oracle等内置了死锁检测机制能够在检测到死锁后自动选择一个事务进行回滚。用户配置允许用户通过配置参数调整死锁检测和解除的策略例如设置超时时间和回滚优先级。优化查询和事务设计通过优化SQL查询和事务设计减少锁的竞争和持有时间降低死锁发生的概率。
7. 两段锁协议Two-Phase Locking, 2PL
7.1. 定义
两段锁协议2PL是一种并发控制协议用于确保数据库事务的可串行化调度。根据该协议每个事务的执行过程被划分为两个阶段扩展阶段Growing Phase 和 收缩阶段Shrinking Phase。
7.2. 阶段划分
7.2.1. 扩展阶段Growing Phase
特点在这个阶段中事务可以申请获得任何数据项上的任何类型的锁S锁或X锁但不能释放任何锁。操作 事务开始时进入扩展阶段。在此期间事务可以根据需要对数据项加锁。一旦事务进入收缩阶段则不能再申请新的锁。
7.2.2. 收缩阶段Shrinking Phase
特点在这个阶段中事务可以释放任何数据项上的任何类型的锁但不能申请新的锁。操作 当事务不再需要获取新锁时进入收缩阶段。在此期间事务可以逐步释放已经持有的锁。事务结束时所有锁必须被释放。
7.3. 可串行化保证
通过将事务的操作严格划分为两个阶段2PL能够确保事务的调度是可串行化的。即多个事务并发执行的结果等价于某个顺序执行这些事务的结果。这有助于维护数据库的一致性和隔离性。
可串行化 vs 串行化
串行化事务一个接一个地执行没有并发。可串行化虽然事务可以并发执行但其最终结果等价于某些串行执行的顺序。换句话说尽管事务是并发执行的但从外部观察它们的效果与某种顺序执行相同。
7.4. 类型
严格的两段锁协议Strict Two-Phase Locking, Strict 2PL
定义在标准2PL的基础上要求事务在提交或回滚之前不释放任何锁。优点防止“脏读”和“不可重复读”提供更强的一致性保证。缺点可能降低并发性能因为锁持有时间更长。
强两段锁协议Strong Two-Phase Locking, Strong 2PL
定义不仅要求事务遵守2PL规则还要求在事务提交前不允许其他事务对该数据项加锁。优点进一步增强了数据一致性。缺点并发性能受到更大限制。
7.5. 示例
假设我们有一个包含两个事务 ( T1 ) 和 ( T2 ) 的银行转账系统涉及三个账户 ( A )、( B ) 和 ( C )。我们将逐步展示这两个事务如何遵循2PL执行。
1. 初始状态
账户 ( A ) 有 100 元。账户 ( B ) 有 200 元。账户 ( C ) 有 300 元。
事务 ( T1 ) 将账户 ( A ) 中的 50 元转到账户 ( B )。
事务 ( T2 )将账户 ( B ) 中的 100 元转到账户 ( C )。
2. 执行过程 #mermaid-svg-QVdDhV97qRWGwFgM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}#mermaid-svg-QVdDhV97qRWGwFgM .error-icon{fill:#552222;}#mermaid-svg-QVdDhV97qRWGwFgM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QVdDhV97qRWGwFgM .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-QVdDhV97qRWGwFgM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QVdDhV97qRWGwFgM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QVdDhV97qRWGwFgM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QVdDhV97qRWGwFgM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QVdDhV97qRWGwFgM .marker{fill:#666;stroke:#666;}#mermaid-svg-QVdDhV97qRWGwFgM .marker.cross{stroke:#666;}#mermaid-svg-QVdDhV97qRWGwFgM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QVdDhV97qRWGwFgM .actor{stroke:hsl(0, 0%, 83%);fill:#eee;}#mermaid-svg-QVdDhV97qRWGwFgM text.actortspan{fill:#333;stroke:none;}#mermaid-svg-QVdDhV97qRWGwFgM .actor-line{stroke:#666;}#mermaid-svg-QVdDhV97qRWGwFgM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-QVdDhV97qRWGwFgM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-QVdDhV97qRWGwFgM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-QVdDhV97qRWGwFgM .sequenceNumber{fill:white;}#mermaid-svg-QVdDhV97qRWGwFgM #sequencenumber{fill:#333;}#mermaid-svg-QVdDhV97qRWGwFgM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-QVdDhV97qRWGwFgM .messageText{fill:#333;stroke:#333;}#mermaid-svg-QVdDhV97qRWGwFgM .labelBox{stroke:hsl(0, 0%, 83%);fill:#eee;}#mermaid-svg-QVdDhV97qRWGwFgM .labelText,#mermaid-svg-QVdDhV97qRWGwFgM .labelTexttspan{fill:#333;stroke:none;}#mermaid-svg-QVdDhV97qRWGwFgM .loopText,#mermaid-svg-QVdDhV97qRWGwFgM .loopTexttspan{fill:#333;stroke:none;}#mermaid-svg-QVdDhV97qRWGwFgM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(0, 0%, 83%);fill:hsl(0, 0%, 83%);}#mermaid-svg-QVdDhV97qRWGwFgM .note{stroke:#999;fill:#666;}#mermaid-svg-QVdDhV97qRWGwFgM .noteText,#mermaid-svg-QVdDhV97qRWGwFgM .noteTexttspan{fill:#fff;stroke:none;}#mermaid-svg-QVdDhV97qRWGwFgM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QVdDhV97qRWGwFgM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QVdDhV97qRWGwFgM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QVdDhV97qRWGwFgM .actorPopupMenu{position:absolute;}#mermaid-svg-QVdDhV97qRWGwFgM .actorPopupMenuPanel{position:absolute;fill:#eee;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-QVdDhV97qRWGwFgM .actor-man line{stroke:hsl(0, 0%, 83%);fill:#eee;}#mermaid-svg-QVdDhV97qRWGwFgM .actor-man circle,#mermaid-svg-QVdDhV97qRWGwFgM line{stroke:hsl(0, 0%, 83%);fill:#eee;stroke-width:2px;}#mermaid-svg-QVdDhV97qRWGwFgM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事务T1 账户A 账户B 账户C 事务T2 加S锁 加X锁 读取余额 (100元) 更新余额为50元 更新余额为250元 提交事务 解放X锁 解放S锁 加S锁 加X锁 读取余额 (250元) 更新余额为150元 更新余额为400元 提交事务 解放X锁 解放S锁 事务T1 账户A 账户B 账户C 事务T2 事务 ( T1 )
1进入扩展阶段
( T1 ) 加S锁共享锁在账户 ( A ) 上。( T1 ) 加X锁排他锁在账户 ( B ) 上。( T1 ) 读取账户 ( A ) 的余额100 元。( T1 ) 更新账户 ( A ) 的余额为 50 元。( T1 ) 更新账户 ( B ) 的余额为 250 元。
2进入收缩阶段
( T1 ) 提交事务。( T1 ) 解放账户 ( B ) 上的 X 锁。( T1 ) 解放账户 ( A ) 上的 S 锁。
事务 ( T2 )
1进入扩展阶段
( T2 ) 加S锁共享锁在账户 ( B ) 上。( T2 ) 加X锁排他锁在账户 ( C ) 上。( T2 ) 读取账户 ( B ) 的余额250 元。( T2 ) 更新账户 ( B ) 的余额为 150 元。( T2 ) 更新账户 ( C ) 的余额为 400 元。
2进入收缩阶段
( T2 ) 提交事务。( T2 ) 解放账户 ( C ) 上的 X 锁。( T2 ) 解放账户 ( B ) 上的 S 锁。
3. 关键点分析
1扩展阶段和收缩阶段的划分
在扩展阶段事务 ( T1 ) 和 ( T2 ) 分别加锁并进行操作。在收缩阶段事务提交后逐步释放锁。
2防止冲突
如果 ( T1 ) 和 ( T2 ) 同时尝试对账户 ( B ) 加锁则会发生冲突。根据2PL规则一个事务必须在另一个事务释放锁之后才能继续操作。例如如果 ( T2 ) 在 ( T1 ) 还未提交时尝试对账户 ( B ) 加锁则 ( T2 ) 必须等待 ( T1 ) 提交并释放锁。
7.6. 优点与缺点
优点
确保可串行化通过严格的阶段划分确保事务的调度是可串行化的。防止数据不一致避免了丢失更新、脏读和不可重复读等问题。
缺点
并发性能较低由于锁持有时间较长可能导致其他事务等待影响并发性能。可能发生死锁如果多个事务相互等待对方释放锁可能会导致死锁。 死锁示例 1. 场景 假设有一个简单的数据库包含两个数据项 A 和 B并且有两个事务 T1 和 T2 需要访问这些数据项。 事务 T1 的操作 1获取 A 的 S 锁。 2获取 B 的 X 锁。 3执行一些操作。 4释放 A 的锁。 5释放 B 的锁。 事务 T2 的操作 1获取 B 的 S 锁。 2获取 A 的 X 锁。 3执行一些操作。 4释放 B 的锁。 5释放 A 的锁。 2. 时间线示例 1初始状态A 和 B 均未被锁定。 2时间点 t1 T1 开始执行并获取 A 的 S 锁。状态A (S, T1)B (无锁) 3时间点 t2 T2 开始执行并获取 B 的 S 锁。状态A (S, T1), B (S, T2) 4时间点 t3 T1 尝试获取 B 的 X 锁但由于 B 已经被 T2 锁定T1 阻塞。状态A (S, T1), B (S, T2) 5时间点 t4 T2 尝试获取 A 的 X 锁但由于 A 已经被 T1 锁定T2 阻塞。状态A (S, T1), B (S, T2) 此时T1 和 T2 都在等待对方释放锁从而形成死锁。 #mermaid-svg-dMMZxD2oQHJTG2ej {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-dMMZxD2oQHJTG2ej .error-icon{fill:#552222;}#mermaid-svg-dMMZxD2oQHJTG2ej .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dMMZxD2oQHJTG2ej .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-dMMZxD2oQHJTG2ej .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dMMZxD2oQHJTG2ej .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dMMZxD2oQHJTG2ej .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dMMZxD2oQHJTG2ej .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dMMZxD2oQHJTG2ej .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dMMZxD2oQHJTG2ej .marker.cross{stroke:#333333;}#mermaid-svg-dMMZxD2oQHJTG2ej svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dMMZxD2oQHJTG2ej .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dMMZxD2oQHJTG2ej text.actortspan{fill:black;stroke:none;}#mermaid-svg-dMMZxD2oQHJTG2ej .actor-line{stroke:grey;}#mermaid-svg-dMMZxD2oQHJTG2ej .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-dMMZxD2oQHJTG2ej .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-dMMZxD2oQHJTG2ej #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-dMMZxD2oQHJTG2ej .sequenceNumber{fill:white;}#mermaid-svg-dMMZxD2oQHJTG2ej #sequencenumber{fill:#333;}#mermaid-svg-dMMZxD2oQHJTG2ej #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-dMMZxD2oQHJTG2ej .messageText{fill:#333;stroke:#333;}#mermaid-svg-dMMZxD2oQHJTG2ej .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dMMZxD2oQHJTG2ej .labelText,#mermaid-svg-dMMZxD2oQHJTG2ej .labelTexttspan{fill:black;stroke:none;}#mermaid-svg-dMMZxD2oQHJTG2ej .loopText,#mermaid-svg-dMMZxD2oQHJTG2ej .loopTexttspan{fill:black;stroke:none;}#mermaid-svg-dMMZxD2oQHJTG2ej .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-dMMZxD2oQHJTG2ej .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-dMMZxD2oQHJTG2ej .noteText,#mermaid-svg-dMMZxD2oQHJTG2ej .noteTexttspan{fill:black;stroke:none;}#mermaid-svg-dMMZxD2oQHJTG2ej .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dMMZxD2oQHJTG2ej .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dMMZxD2oQHJTG2ej .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dMMZxD2oQHJTG2ej .actorPopupMenu{position:absolute;}#mermaid-svg-dMMZxD2oQHJTG2ej .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-dMMZxD2oQHJTG2ej .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dMMZxD2oQHJTG2ej .actor-man circle,#mermaid-svg-dMMZxD2oQHJTG2ej line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-dMMZxD2oQHJTG2ej :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事务T1 事务T2 A B 初始状态A 和 B 均未被锁定 获取 A 的 S 锁 状态A (S, T1)B (无锁) 获取 B 的 S 锁 状态A (S, T1), B (S, T2) 尝试获取 B 的 X 锁 T1 阻塞等待 T2 释放 B 的锁 尝试获取 A 的 X 锁 T2 阻塞等待 T1 释放 A 的锁 形成死锁 事务T1 事务T2 A B 3. 示例代码 为了更清晰地展示上述死锁情况以下是伪代码示例 public class TwoPhaseLockingExample {public static void main(String[] args) {final Object A new Object();final Object B new Object();Thread t1 new Thread(() - {synchronized (A) {System.out.println(T1: Locked A);try {Thread.sleep(100); // 模拟其他操作} catch (InterruptedException e) {e.printStackTrace();}synchronized (B) {System.out.println(T1: Locked B);// 执行操作System.out.println(T1: Releasing B);}System.out.println(T1: Releasing A);}});Thread t2 new Thread(() - {synchronized (B) {System.out.println(T2: Locked B);try {Thread.sleep(100); // 模拟其他操作} catch (InterruptedException e) {e.printStackTrace();}synchronized (A) {System.out.println(T2: Locked A);// 执行操作System.out.println(T2: Releasing A);}System.out.println(T2: Releasing B);}});t1.start();t2.start();}
}运行上述代码您会看到 T1 和 T2 进入死锁状态。通过日志输出可以看到它们分别持有不同的锁并等待对方释放锁。 8. 三级封锁协议
8.1. 定义
三级封锁协议是数据库系统中用于确保事务隔离性的三种不同级别的封锁规则。每级封锁协议都对事务加锁提出了不同的要求以保证不同程度的数据一致性和并发性。
8.2. 一级封锁协议1PL
8.2.1. 定义
一级封锁协议One-Phase Locking Protocol, 1PL是数据库管理系统中最基本的并发控制协议之一。它要求事务在修改数据项之前必须获得排他锁X锁并在事务结束时释放所有锁。该协议主要用于防止丢失更新问题。
8.2.2. 规则
加锁规则事务在修改数据项之前必须获得排他锁X锁。解锁规则所有锁必须在事务结束时释放即事务提交或回滚后才能释放锁。
8.2.3. 示例
假设我们有两个事务 ( T1 ) 和 ( T2 )它们分别对账户 ( A ) 进行操作 #mermaid-svg-VEzy8bux2QdVejf7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-VEzy8bux2QdVejf7 .error-icon{fill:#552222;}#mermaid-svg-VEzy8bux2QdVejf7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VEzy8bux2QdVejf7 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-VEzy8bux2QdVejf7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VEzy8bux2QdVejf7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VEzy8bux2QdVejf7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VEzy8bux2QdVejf7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VEzy8bux2QdVejf7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VEzy8bux2QdVejf7 .marker.cross{stroke:#333333;}#mermaid-svg-VEzy8bux2QdVejf7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VEzy8bux2QdVejf7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VEzy8bux2QdVejf7 .cluster-label text{fill:#333;}#mermaid-svg-VEzy8bux2QdVejf7 .cluster-label span{color:#333;}#mermaid-svg-VEzy8bux2QdVejf7 .label text,#mermaid-svg-VEzy8bux2QdVejf7 span{fill:#333;color:#333;}#mermaid-svg-VEzy8bux2QdVejf7 .node rect,#mermaid-svg-VEzy8bux2QdVejf7 .node circle,#mermaid-svg-VEzy8bux2QdVejf7 .node ellipse,#mermaid-svg-VEzy8bux2QdVejf7 .node polygon,#mermaid-svg-VEzy8bux2QdVejf7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VEzy8bux2QdVejf7 .node .label{text-align:center;}#mermaid-svg-VEzy8bux2QdVejf7 .node.clickable{cursor:pointer;}#mermaid-svg-VEzy8bux2QdVejf7 .arrowheadPath{fill:#333333;}#mermaid-svg-VEzy8bux2QdVejf7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VEzy8bux2QdVejf7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VEzy8bux2QdVejf7 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-VEzy8bux2QdVejf7 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-VEzy8bux2QdVejf7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VEzy8bux2QdVejf7 .cluster text{fill:#333;}#mermaid-svg-VEzy8bux2QdVejf7 .cluster span{color:#333;}#mermaid-svg-VEzy8bux2QdVejf7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VEzy8bux2QdVejf7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-VEzy8bux2QdVejf7 .transaction1*{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#mermaid-svg-VEzy8bux2QdVejf7 .transaction1 span{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#mermaid-svg-VEzy8bux2QdVejf7 .transaction2*{fill:#69f!important;stroke:#333!important;stroke-width:4px!important;}#mermaid-svg-VEzy8bux2QdVejf7 .transaction2 span{fill:#69f!important;stroke:#333!important;stroke-width:4px!important;} 事务 T2 事务 T1 T1 加锁成功 T1 解锁后 等待 T1 提交或回滚 尝试加X锁 (A) 加X锁 (A) 更新 A 提交事务 解放 X 锁 (A) 更新 A 加X锁 (A) 提交事务 解放 X 锁 (A) 关键点分析
1加X锁
( T1 ) 在更新账户 ( A ) 之前加了X锁并且在提交之前一直持有该锁。( T2 ) 尝试对账户 ( A ) 加X锁时必须等待 ( T1 ) 提交或回滚后才能继续。
2更新操作
( T1 ) 和 ( T2 ) 分别对账户 ( A ) 进行更新操作但 ( T2 ) 必须等待 ( T1 ) 完成并释放锁。
3提交与解锁
( T1 ) 提交事务后释放X锁此时 ( T2 ) 才能获取X锁并进行更新操作。
4等待过程
明确展示了 ( T2 ) 在尝试加X锁时进入等待状态直到 ( T1 ) 提交或回滚。
8.2.4. 优缺点
优点
简单易实现只需要关注更新操作不需要复杂的锁管理机制。对于简单的应用场景1PL易于实现和维护。防止丢失更新确保了更新操作的一致性避免了数据覆盖问题。较低的开销相比于更严格的封锁协议1PL的锁管理开销较低因为它只涉及更新操作的X锁管理对读取操作没有特殊要求。
缺点
并发性能较低由于所有更新操作都需要加X锁可能会导致其他事务长时间等待影响并发性能。不能防止脏读和不可重复读对于读取操作没有限制因此无法防止事务读取未提交的数据脏读或在同一事务中多次读取同一数据项时结果不一致不可重复读。潜在的死锁风险如果多个事务对多个数据项进行复杂的加锁操作可能会形成循环等待导致死锁。虽然可以通过死锁检测或预防机制来解决但这增加了系统的复杂性。
8.2.5. 实际应用
一级封锁协议适用于对一致性要求不高、但对性能要求较高的场景。例如在一些简单的应用程序中可能只需要确保更新操作的一致性而对读取操作的隔离性要求不高。具体应用场景包括
批处理系统主要进行批量更新操作读取较少。日志记录系统确保写入操作的一致性。简单数据库应用如小型企业的库存管理系统或任务调度系统。历史数据分析以读取为主更新较少。嵌入式系统资源有限对性能和开销有较高要求。
8.3. 二级封锁协议2PL
8.3.1. 定义
二级封锁协议Two-Phase Locking Protocol, 2PL是一种用于数据库管理系统中控制并发事务的协议。它确保事务在读取或修改数据项之前获得适当的锁并且在事务结束前不释放任何锁以保证数据的一致性和隔离性。
8.3.2. 规则
加锁规则 事务在读取数据项之前必须获得共享锁S锁。事务在修改数据项之前必须获得排他锁X锁。 解锁规则 所有锁必须在事务结束前保持不变即事务不能在提交或回滚之前释放任何锁。
8.3.3. 示例
假设我们有两个事务 ( T1 ) 和 ( T2 )它们分别对账户 ( A ) 进行操作 #mermaid-svg-umtSX7sZZE2Dt4X1 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-umtSX7sZZE2Dt4X1 .error-icon{fill:#552222;}#mermaid-svg-umtSX7sZZE2Dt4X1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-umtSX7sZZE2Dt4X1 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-umtSX7sZZE2Dt4X1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-umtSX7sZZE2Dt4X1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-umtSX7sZZE2Dt4X1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-umtSX7sZZE2Dt4X1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-umtSX7sZZE2Dt4X1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-umtSX7sZZE2Dt4X1 .marker.cross{stroke:#333333;}#mermaid-svg-umtSX7sZZE2Dt4X1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-umtSX7sZZE2Dt4X1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-umtSX7sZZE2Dt4X1 .cluster-label text{fill:#333;}#mermaid-svg-umtSX7sZZE2Dt4X1 .cluster-label span{color:#333;}#mermaid-svg-umtSX7sZZE2Dt4X1 .label text,#mermaid-svg-umtSX7sZZE2Dt4X1 span{fill:#333;color:#333;}#mermaid-svg-umtSX7sZZE2Dt4X1 .node rect,#mermaid-svg-umtSX7sZZE2Dt4X1 .node circle,#mermaid-svg-umtSX7sZZE2Dt4X1 .node ellipse,#mermaid-svg-umtSX7sZZE2Dt4X1 .node polygon,#mermaid-svg-umtSX7sZZE2Dt4X1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-umtSX7sZZE2Dt4X1 .node .label{text-align:center;}#mermaid-svg-umtSX7sZZE2Dt4X1 .node.clickable{cursor:pointer;}#mermaid-svg-umtSX7sZZE2Dt4X1 .arrowheadPath{fill:#333333;}#mermaid-svg-umtSX7sZZE2Dt4X1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-umtSX7sZZE2Dt4X1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-umtSX7sZZE2Dt4X1 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-umtSX7sZZE2Dt4X1 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-umtSX7sZZE2Dt4X1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-umtSX7sZZE2Dt4X1 .cluster text{fill:#333;}#mermaid-svg-umtSX7sZZE2Dt4X1 .cluster span{color:#333;}#mermaid-svg-umtSX7sZZE2Dt4X1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-umtSX7sZZE2Dt4X1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-umtSX7sZZE2Dt4X1 .transaction1*{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#mermaid-svg-umtSX7sZZE2Dt4X1 .transaction1 span{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#mermaid-svg-umtSX7sZZE2Dt4X1 .transaction2*{fill:#69f!important;stroke:#333!important;stroke-width:4px!important;}#mermaid-svg-umtSX7sZZE2Dt4X1 .transaction2 span{fill:#69f!important;stroke:#333!important;stroke-width:4px!important;} 事务 T2 事务 T1 T1 加锁成功 T1 解锁后 T1 提交后 等待 T1 解放 S 锁 尝试加S锁 (A) 加S锁 (A) 读取 A 解放 S 锁 (A) 尝试加X锁 (A) 等待 T1 提交或回滚 加X锁 (A) 更新 A 提交事务 解放 X 锁 (A) 读取 A 加S锁 (A) 解放 S 锁 (A) 加X锁 (A) 更新 A 提交事务 解放 X 锁 (A) 关键点分析
1加S锁
( T1 ) 在读取账户 ( A ) 之前加了S锁并在读取完成后立即释放该锁。( T2 ) 尝试对账户 ( A ) 加S锁时必须等待 ( T1 ) 释放S锁后才能继续。
2加X锁
( T1 ) 在更新账户 ( A ) 之前加了X锁并且在提交之前一直持有该锁。( T2 ) 尝试对账户 ( A ) 加X锁时必须等待 ( T1 ) 提交或回滚后才能继续。
3等待过程
( T2 ) 在尝试加S锁时进入等待状态直到 ( T1 ) 释放S锁。( T2 ) 在尝试加X锁时进入等待状态直到 ( T1 ) 提交并释放X锁。
8.3.4. 优缺点
优点
防止丢失更新确保了更新操作的一致性避免了数据覆盖问题。防止脏读确保事务只能读取已经提交的数据提高了数据的一致性和可靠性。相对简单的实现相比于更严格的三级封锁协议2PL的实现较为简单容易理解和维护。较高的并发性能允许多个事务同时持有共享锁S锁从而提高读操作的并发性能。
缺点
无法防止不可重复读如果一个事务在同一事务中多次读取同一数据项可能会因为其他事务的更新而导致结果不一致。并发性能受限对于频繁的写操作由于X锁需要持有到事务结束可能会导致其他事务长时间等待影响并发性能。潜在的死锁风险如果多个事务对多个数据项进行复杂的加锁操作可能会形成循环等待导致死锁。
8.3.5. 实际应用
二级封锁协议适用于对一致性要求较高且读多写少的场景。具体应用场景包括
在线交易系统确保账户余额等关键数据的一致性。银行系统确保转账、存款和取款操作的高度一致性和隔离性。电子商务平台确保商品库存管理的准确性避免超卖等问题。医疗信息系统确保患者记录和诊断数据的一致性和隔离性。物流管理系统确保订单处理和库存管理的数据一致性。
8.4. 三级封锁协议3PL
8.4.1. 定义
三级封锁协议Three-Phase Locking Protocol, 3PL是数据库管理系统中用于控制并发事务的一种高级机制。它在二级封锁协议的基础上进一步加强了锁的管理确保事务在读取数据项时只能加共享锁S锁并且在事务结束前不能释放任何锁。这有效地解决了不可重复读和幻读问题。
8.4.2. 规则
加锁规则 事务在读取数据项之前必须获得共享锁S锁。事务在修改数据项之前必须获得排他锁X锁。 解锁规则 所有锁必须在事务结束前保持不变即事务不能在提交或回滚之前释放任何锁。事务在读取数据项时不能加排他锁X锁只能加共享锁S锁。
8.4.3. 示例
假设我们有两个事务 ( T1 ) 和 ( T2 )它们分别对账户 ( A ) 进行操作 #mermaid-svg-mAsIBJx0gE855aFj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-mAsIBJx0gE855aFj .error-icon{fill:#552222;}#mermaid-svg-mAsIBJx0gE855aFj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mAsIBJx0gE855aFj .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-mAsIBJx0gE855aFj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mAsIBJx0gE855aFj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mAsIBJx0gE855aFj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mAsIBJx0gE855aFj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mAsIBJx0gE855aFj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mAsIBJx0gE855aFj .marker.cross{stroke:#333333;}#mermaid-svg-mAsIBJx0gE855aFj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mAsIBJx0gE855aFj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mAsIBJx0gE855aFj .cluster-label text{fill:#333;}#mermaid-svg-mAsIBJx0gE855aFj .cluster-label span{color:#333;}#mermaid-svg-mAsIBJx0gE855aFj .label text,#mermaid-svg-mAsIBJx0gE855aFj span{fill:#333;color:#333;}#mermaid-svg-mAsIBJx0gE855aFj .node rect,#mermaid-svg-mAsIBJx0gE855aFj .node circle,#mermaid-svg-mAsIBJx0gE855aFj .node ellipse,#mermaid-svg-mAsIBJx0gE855aFj .node polygon,#mermaid-svg-mAsIBJx0gE855aFj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mAsIBJx0gE855aFj .node .label{text-align:center;}#mermaid-svg-mAsIBJx0gE855aFj .node.clickable{cursor:pointer;}#mermaid-svg-mAsIBJx0gE855aFj .arrowheadPath{fill:#333333;}#mermaid-svg-mAsIBJx0gE855aFj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mAsIBJx0gE855aFj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mAsIBJx0gE855aFj .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-mAsIBJx0gE855aFj .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-mAsIBJx0gE855aFj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mAsIBJx0gE855aFj .cluster text{fill:#333;}#mermaid-svg-mAsIBJx0gE855aFj .cluster span{color:#333;}#mermaid-svg-mAsIBJx0gE855aFj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mAsIBJx0gE855aFj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-mAsIBJx0gE855aFj .transaction1*{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#mermaid-svg-mAsIBJx0gE855aFj .transaction1 span{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#mermaid-svg-mAsIBJx0gE855aFj .transaction2*{fill:#69f!important;stroke:#333!important;stroke-width:4px!important;}#mermaid-svg-mAsIBJx0gE855aFj .transaction2 span{fill:#69f!important;stroke:#333!important;stroke-width:4px!important;} 事务 T2 事务 T1 T1 提交后 等待 T1 提交或回滚 尝试加S锁 (A) 加S锁 (A) 读取 A 提交事务 解放 S 锁 (A) 读取 A 加S锁 (A) 加X锁 (A) 更新 A 提交事务 解放 S 锁 (A)解放 X 锁 (A) 关键点分析 1加S锁
( T1 ) 在读取账户 ( A ) 之前加了S锁并且在整个事务期间保持该锁。( T2 ) 尝试对账户 ( A ) 加S锁时必须等待 ( T1 ) 提交或回滚后才能继续。
2加X锁
( T1 ) 在更新账户 ( A ) 之前加了X锁并且在提交之前一直持有该锁。( T2 ) 在 ( T1 ) 提交后才能获取S锁进行读取操作。
3等待过程
( T2 ) 在尝试加S锁时进入等待状态直到 ( T1 ) 提交或回滚。( T2 ) 在 ( T1 ) 提交后才能获取S锁进行读取操作。
8.4.4. 优缺点
优点
防止丢失更新确保了更新操作的一致性避免了数据覆盖问题。防止脏读确保事务只能读取已经提交的数据提高了数据的一致性和可靠性。防止不可重复读确保事务在同一事务中多次读取同一数据项时结果是一致的。高一致性保障提供了最强的一致性保证适用于对数据一致性要求极高的场景。
缺点
并发性能较低由于读取操作也需要持有S锁到事务结束可能会导致其他事务长时间等待影响并发性能。复杂度增加需要更复杂的锁管理机制增加了系统的复杂度。潜在的死锁风险如果多个事务对多个数据项进行复杂的加锁操作可能会形成循环等待导致死锁。
8.4.5. 实际应用
三级封锁协议适用于对数据一致性要求极高的场景。具体应用场景包括
金融系统如账务处理、证券交易等需要确保数据的高度一致性和隔离性。航空订票系统确保航班座位分配的准确性和一致性。医疗信息系统确保患者记录和诊断数据的高度一致性和隔离性。分布式数据库系统确保跨节点数据的一致性和隔离性。