原子性(Atomicity)
原子性指的是事务中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个点。如果事务中的某个操作失败,整个事务将被回滚到开始状态,就像这个事务从未执行过一样。
实现逻辑:
- 日志记录:在事务开始时,数据库会记录一个日志,包括事务的所有操作。这个日志是原子性的,即要么全部写入,要么全部不写入。
- 写前日志(Write-Ahead Logging, WAL):在执行任何修改操作之前,首先将操作记录到日志中。这样,即使在操作过程中发生故障,也可以通过日志来恢复数据。
- 回滚操作:如果事务中的某个操作失败,数据库会使用日志中的信息来执行回滚操作,撤销事务中已经执行的所有操作。
一致性(Consistency)
一致性确保数据库从一个一致的状态转移到另一个一致的状态。在事务开始之前和提交之后,所有的数据都应满足预定义的完整性约束。
实现逻辑:
- 完整性约束:数据库通过预定义的规则(如主键、外键、检查约束等)来确保数据的一致性。
- 事务隔离:通过事务隔离级别来控制多个事务之间的并发访问,防止脏读、不可重复读和幻读,从而保证一致性。
- 锁定机制:数据库使用锁(如行锁、表锁)来控制对数据的并发访问,确保在事务执行期间数据的一致性不被破坏。
- 恢复机制:在系统故障时,数据库可以使用日志来恢复到一致的状态。例如,如果事务在提交前失败,可以使用日志来回滚事务;如果事务已经提交但在写入磁盘前系统崩溃,可以使用日志来重做事务。
业务案例
银行转账事务,涉及两个账户A和B,A向B转账100元。
- 开始事务:记录事务开始的日志。
- 检查一致性:确保A账户有足够的余额。
- 执行操作:从A账户扣除100元,记录操作到日志。
- 记录日志:在B账户增加100元之前,先记录这个操作到日志。
- 提交事务:将所有操作应用到数据库,并记录提交日志。
- 故障恢复:如果在步骤5之前发生故障,数据库可以使用日志来回滚到事务开始前的状态,确保数据一致性。
区别
- 关注点不同:原子性关注的是事务作为一个整体的执行结果,而一致性关注的是事务执行后数据的准确性和完整性。
- 实现机制不同:原子性主要通过日志记录和回滚机制来实现,而一致性则通过完整性约束、事务隔离、锁定机制和恢复机制来实现。
- 影响范围不同:原子性影响的是单个事务的执行,而一致性影响的是整个数据库的状态和多个事务的执行结果。
原子性(Atomicity)实现
- 开始事务:
sql
代码解读
复制代码
START TRANSACTION;
- 执行一系列数据库操作:
sql
代码解读
复制代码
-- 假设我们有两个账户,A和B,A向B转账100元 UPDATE accounts SET balance = balance - 100 WHERE account_id = 1; -- A账户扣款 UPDATE accounts SET balance = balance + 100 WHERE account_id = 2; -- B账户收款
- 提交事务(如果所有操作都成功):
sql
代码解读
复制代码
COMMIT;
- 回滚事务(如果操作中有任何失败):
sql
代码解读
复制代码
ROLLBACK;
java实现
java
代码解读
复制代码
Connection conn = null; try { // 获取数据库连接 conn = dataSource.getConnection(); // 开始事务 conn.setAutoCommit(false); // 执行一系列数据库操作 conn.prepareStatement("UPDATE accounts SET balance = balance - 100 WHERE account_id = 1").executeUpdate(); conn.prepareStatement("UPDATE accounts SET balance = balance + 100 WHERE account_id = 2").executeUpdate(); // 提交事务 conn.commit(); } catch (SQLException e) { // 发生异常时回滚事务 if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { // 记录回滚时的异常 ex.printStackTrace(); } } // 处理或抛出异常 throw new RuntimeException("Transaction failed", e); } finally { // 关闭数据库连接 if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
一致性(Consistency)实现
- 设置完整性约束(在创建表时定义):
sql
代码解读
复制代码
CREATE TABLE accounts ( account_id INT PRIMARY KEY, balance DECIMAL(10, 2) CHECK (balance >= 0) -- 确保余额非负 );
- 使用事务隔离级别(在开始事务时设置):
sql
代码解读
复制代码
-- 以MySQL为例,设置隔离级别为可重复读 START TRANSACTION WITH CONSISTENCY;
- 使用锁定机制(在SQL语句中显式加锁):
sql
代码解读
复制代码
-- 对A账户进行排它锁 SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE;
- 检查一致性条件(在事务逻辑中实现):
sql
代码解读
复制代码
-- 检查A账户余额是否足够 SELECT balance FROM accounts WHERE account_id = 1; -- 如果余额不足,可以执行ROLLBACK来回滚事务
- 执行操作并提交事务:
sql
代码解读
复制代码
-- 执行转账操作 UPDATE accounts SET balance = balance - 100 WHERE account_id = 1; UPDATE accounts SET balance = balance + 100 WHERE account_id = 2; COMMIT;
Java实现
java
代码解读
复制代码
Connection conn = null; try { // 获取数据库连接 conn = dataSource.getConnection(); // 开始事务 conn.setAutoCommit(false); // 执行一系列数据库操作 conn.prepareStatement("UPDATE accounts SET balance = balance - 100 WHERE account_id = 1").executeUpdate(); conn.prepareStatement("UPDATE accounts SET balance = balance + 100 WHERE account_id = 2").executeUpdate(); // 提交事务 conn.commit(); } catch (SQLException e) { // 发生异常时回滚事务 if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { // 记录回滚时的异常 ex.printStackTrace(); } } // 处理或抛出异常 throw new RuntimeException("Transaction failed", e); } finally { // 关闭数据库连接 if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
综合案例
综合展示如何在一个事务中实现原子性和一致性:
sql
代码解读
复制代码
sql -- 开始事务 BEGIN TRANSACTION; -- 检查A账户余额是否足够 SELECT balance FROM accounts WHERE account_id = 1; -- 如果余额不足,回滚事务 IF balance < 100 THEN ROLLBACK TRANSACTION; ELSE -- 执行转账操作 UPDATE accounts SET balance = balance - 100 WHERE account_id = 1; UPDATE accounts SET balance = balance + 100 WHERE account_id = 2; -- 提交事务 COMMIT TRANSACTION; END IF;
手写原子性与一致性案例1
Java来实现这个简单的数据库系统(主要让大家理解原子性与一致性的逻辑,并非实现一个真实的数据库的能力
)。这个系统将包括以下功能:
- 原子性:通过事务来保证操作的原子性,如果事务中的任何操作失败,整个事务将被回滚。
- 一致性:通过一致性检查来确保数据在事务前后满足预定义的规则。
java
代码解读
复制代码
import java.util.*; import java.util.concurrent.locks.*; public class SimpleDatabase { // 存储数据的内存结构 private final Map<String, Integer> storage = new HashMap<>(); // 用于控制并发访问的读写锁 private final Lock writeLock = new ReentrantLock(); private final Lock readLock = new ReentrantLock(); // 执行事务 public void executeTransaction(List<DatabaseOperation> operations) { writeLock.lock(); try { // 首先进行预检查,确保事务可以执行 for (DatabaseOperation op : operations) { if (!isConsistent(op)) { throw new IllegalStateException("Inconsistent operation detected"); } } // 执行所有操作 for (DatabaseOperation op : operations) { applyOperation(op); } } finally { writeLock.unlock(); } } // 一致性检查 private boolean isConsistent(DatabaseOperation operation) { // 这里可以添加具体的一致性检查逻辑 // 检查账户余额是否足够 if (operation.getType() == DatabaseOperation.Type.DEBIT && storage.getOrDefault(operation.getKey(), 0) < operation.getValue()) { return false; } return true; } // 应用操作 private void applyOperation(DatabaseOperation operation) { switch (operation.getType()) { case CREDIT: storage.merge(operation.getKey(), operation.getValue(), Integer::sum); break; case DEBIT: storage.compute(operation.getKey(), (k, v) -> v - operation.getValue()); break; default: throw new IllegalArgumentException("Unknown operation type"); } } // 定义操作类型 enum OperationType { CREDIT, DEBIT } // 数据库操作封装 static class DatabaseOperation { private final String key; private final int value; private final OperationType type; public DatabaseOperation(String key, int value, OperationType type) { this.key = key; this.value = value; this.type = type; } public String getKey() { return key; } public int getValue() { return value; } public OperationType getType() { return type; } } // 测试 public static void main(String[] args) { SimpleDatabase db = new SimpleDatabase(); // 创建事务 List<DatabaseOperation> transaction = new ArrayList<>(); transaction.add(new DatabaseOperation("account1", 100, OperationType.CREDIT)); transaction.add(new DatabaseOperation("account2", 50, OperationType.DEBIT)); // 执行事务 db.executeTransaction(transaction); // 打印结果 System.out.println(db.storage); } }
SimpleDatabase
类提供了一个简单的内存数据库实现。我们定义了一个DatabaseOperation
类来封装数据库操作,包括键、值和操作类型(借记或贷记)。executeTransaction
方法用于执行一个事务,它首先进行一致性检查,然后应用所有操作。如果任何操作不一致,事务将抛出异常并回滚。展示了如何在内存中以编程方式实现原子性和一致性,但它并不是一个真正的数据库系统。真正的数据库系统需要处理更多的复杂性,如持久化、并发控制、恢复机制等
简单的银行账户管理系统,它将包括以下特性:
- 原子性:通过事务机制保证操作的原子性,如果事务中的任何一步失败,整个事务将回滚。
- 一致性:通过业务逻辑检查保证数据的一致性,例如,账户余额不能为负。
我们将使用Java的ConcurrentHashMap
来保证线程安全,使用ReentrantReadWriteLock
来实现读写锁,以提高并发性能。
手写原子性与一致性案例2
银行账户管理系统实现
java
代码解读
复制代码
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class BankAccountSystem { // 使用线程安全的HashMap来存储账户数据 private final ConcurrentHashMap<String, Account> accounts = new ConcurrentHashMap<>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); // 账户类 private static class Account { int balance; public Account(int initialBalance) { this.balance = initialBalance; } } // 创建新账户 public void createAccount(String accountId, int initialBalance) { writeLock.lock(); try { accounts.compute(accountId, (k, v) -> { if (v == null) { return new Account(initialBalance); } throw new IllegalStateException("Account already exists"); }); } finally { writeLock.unlock(); } } // 存款操作 public void deposit(String accountId, int amount) { writeLock.lock(); try { Account account = accounts.get(accountId); if (account == null) { throw new IllegalArgumentException("Account does not exist"); } account.balance += amount; } finally { writeLock.unlock(); } } // 取款操作 public void withdraw(String accountId, int amount) throws InsufficientFundsException { writeLock.lock(); try { Account account = accounts.get(accountId); if (account == null) { throw new IllegalArgumentException("Account does not exist"); } if (account.balance < amount) { throw new InsufficientFundsException("Insufficient funds for account: " + accountId); } account.balance -= amount; } finally { writeLock.unlock(); } } // 检查账户余额 public int getBalance(String accountId) { readLock.lock(); try { Account account = accounts.get(accountId); return account != null ? account.balance : 0; } finally { readLock.unlock(); } } // 自定义异常:资金不足异常 public static class InsufficientFundsException extends Exception { public InsufficientFundsException(String message) { super(message); } } // 测试 public static void main(String[] args) { BankAccountSystem bankSystem = new BankAccountSystem(); bankSystem.createAccount("001", 1000); try { bankSystem.deposit("001", 500); bankSystem.withdraw("001", 1600); // 这将抛出异常,因为余额不足 } catch (InsufficientFundsException e) { System.out.println(e.getMessage()); } System.out.println("Balance after transactions: " + bankSystem.getBalance("001")); } }
此案例定义了一个BankAccountSystem
类,它使用ConcurrentHashMap
来存储账户信息,并使用ReentrantReadWriteLock
来保证读写操作的线程安全。我们提供了createAccount
、deposit
、withdraw
和getBalance
方法来执行账户操作。withdraw
方法在余额不足时会抛出InsufficientFundsException
异常,这是一致性检查的一部分
结论
原子性和一致性是数据库事务不可或缺的特性,它们共同维护了数据的完整性和可靠性。作为架构师或开发者,理解这些概念并掌握其实现细节对于设计和维护高效、稳定的数据库系统至关重要。