首页 > 数据库 >架构师手写代码:分享数据库原子性与一致性实现方案(不再背概念)

架构师手写代码:分享数据库原子性与一致性实现方案(不再背概念)

时间:2024-09-24 20:02:03浏览次数:8  
标签:account 数据库 事务 一致性 架构师 手写 balance conn

image

数据库事务的原子性和一致性是数据库管理系统(DBMS)中确保数据完整性和可靠性的两个关键属性。下面是这两个属性的基本概念和实现逻辑:

肖哥弹架构 跟大家“弹弹” 数据库设计技巧, 关注公号回复 'mvcc' 获得手写数据库事务代码

欢迎 点赞,点赞,点赞。

关注公号Solomon肖哥弹架构获取更多精彩内容

历史热点文章

原子性(Atomicity)

原子性指的是事务中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个点。如果事务中的某个操作失败,整个事务将被回滚到开始状态,就像这个事务从未执行过一样。

实现逻辑:

  1. 日志记录:在事务开始时,数据库会记录一个日志,包括事务的所有操作。这个日志是原子性的,即要么全部写入,要么全部不写入。
  2. 写前日志(Write-Ahead Logging, WAL):在执行任何修改操作之前,首先将操作记录到日志中。这样,即使在操作过程中发生故障,也可以通过日志来恢复数据。
  3. 回滚操作:如果事务中的某个操作失败,数据库会使用日志中的信息来执行回滚操作,撤销事务中已经执行的所有操作。

一致性(Consistency)

一致性确保数据库从一个一致的状态转移到另一个一致的状态。在事务开始之前和提交之后,所有的数据都应满足预定义的完整性约束。

实现逻辑:

  1. 完整性约束:数据库通过预定义的规则(如主键、外键、检查约束等)来确保数据的一致性。
  2. 事务隔离:通过事务隔离级别来控制多个事务之间的并发访问,防止脏读、不可重复读和幻读,从而保证一致性。
  3. 锁定机制:数据库使用锁(如行锁、表锁)来控制对数据的并发访问,确保在事务执行期间数据的一致性不被破坏。
  4. 恢复机制:在系统故障时,数据库可以使用日志来恢复到一致的状态。例如,如果事务在提交前失败,可以使用日志来回滚事务;如果事务已经提交但在写入磁盘前系统崩溃,可以使用日志来重做事务。

业务案例

银行转账事务,涉及两个账户A和B,A向B转账100元。

  1. 开始事务:记录事务开始的日志。
  2. 检查一致性:确保A账户有足够的余额。
  3. 执行操作:从A账户扣除100元,记录操作到日志。
  4. 记录日志:在B账户增加100元之前,先记录这个操作到日志。
  5. 提交事务:将所有操作应用到数据库,并记录提交日志。
  6. 故障恢复:如果在步骤5之前发生故障,数据库可以使用日志来回滚到事务开始前的状态,确保数据一致性。

区别

  • 关注点不同:原子性关注的是事务作为一个整体的执行结果,而一致性关注的是事务执行后数据的准确性和完整性。
  • 实现机制不同:原子性主要通过日志记录和回滚机制来实现,而一致性则通过完整性约束、事务隔离、锁定机制和恢复机制来实现。
  • 影响范围不同:原子性影响的是单个事务的执行,而一致性影响的是整个数据库的状态和多个事务的执行结果。

原子性(Atomicity)实现

  1. 开始事务
START TRANSACTION;
  1. 执行一系列数据库操作
-- 假设我们有两个账户,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账户收款
  1. 提交事务(如果所有操作都成功):
COMMIT;
  1. 回滚事务(如果操作中有任何失败):
ROLLBACK;

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)实现

  1. 设置完整性约束(在创建表时定义):
CREATE TABLE accounts (
    account_id INT PRIMARY KEY,
    balance DECIMAL(10, 2) CHECK (balance >= 0) -- 确保余额非负
);
  1. 使用事务隔离级别(在开始事务时设置):
-- 以MySQL为例,设置隔离级别为可重复读
START TRANSACTION WITH CONSISTENCY;
  1. 使用锁定机制(在SQL语句中显式加锁):
-- 对A账户进行排它锁
SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE;
  1. 检查一致性条件(在事务逻辑中实现):
-- 检查A账户余额是否足够
SELECT balance FROM accounts WHERE account_id = 1;
-- 如果余额不足,可以执行ROLLBACK来回滚事务
  1. 执行操作并提交事务
-- 执行转账操作
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT;

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
-- 开始事务
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来实现这个简单的数据库系统(主要让大家理解原子性与一致性的逻辑,并非实现一个真实的数据库的能力)。这个系统将包括以下功能:

  1. 原子性:通过事务来保证操作的原子性,如果事务中的任何操作失败,整个事务将被回滚。
  2. 一致性:通过一致性检查来确保数据在事务前后满足预定义的规则。
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 方法用于执行一个事务,它首先进行一致性检查,然后应用所有操作。如果任何操作不一致,事务将抛出异常并回滚。展示了如何在内存中以编程方式实现原子性和一致性,但它并不是一个真正的数据库系统。真正的数据库系统需要处理更多的复杂性,如持久化、并发控制、恢复机制等

简单的银行账户管理系统,它将包括以下特性:

  1. 原子性:通过事务机制保证操作的原子性,如果事务中的任何一步失败,整个事务将回滚。
  2. 一致性:通过业务逻辑检查保证数据的一致性,例如,账户余额不能为负。

我们将使用Java的ConcurrentHashMap来保证线程安全,使用ReentrantReadWriteLock来实现读写锁,以提高并发性能。

手写原子性与一致性案例2

银行账户管理系统实现

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来保证读写操作的线程安全。我们提供了createAccountdepositwithdrawgetBalance方法来执行账户操作。withdraw方法在余额不足时会抛出InsufficientFundsException异常,这是一致性检查的一部分

结论

原子性和一致性是数据库事务不可或缺的特性,它们共同维护了数据的完整性和可靠性。作为架构师或开发者,理解这些概念并掌握其实现细节对于设计和维护高效、稳定的数据库系统至关重要。

标签:account,数据库,事务,一致性,架构师,手写,balance,conn
From: https://www.cnblogs.com/xiaoge-it/p/18429902

相关文章

  • 一位架构师的自述:在尚未踏入的世界成为你自己
    这是我参与创作者计划的第1篇文章 我叫艾佳,工作经验14年,编程经验30年。我来自智能平台部,负责标签平台、标签圈人、标签选品、EasyData、算法数据流的架构工作。致力于批量计算、流式计算、交互式计算的通用化数据应用构建,降低大数据计算的使用门槛。在此,我跟大家分享一下我......
  • 20年架构师用一文带你彻底搞懂SpringBoot嵌入式Web容器原理
    ContainerLess理念微服务把应用和它所依赖的组件包、配置文件及附带的运行脚本打包成一个单一、独立、可执行的jar包文件。在实现Web服务器时,几乎不需要任何配置就可以启动Tomcat。你只需要使用java-jar命令就可以让Tomcat成为SpringBoot的一个自包含的可运行组件和单元。同时,这......
  • 项目实战:一步步实现高效缓存与数据库的数据一致性方案
    Hello,大家好!我是积极活泼、爱分享技术的小米!今天我们来聊一聊在做个人项目时,如何保证数据一致性。数据一致性问题,尤其是涉及缓存与数据库的场景,可以说是我们日常开发中经常遇到的挑战之一。今天我将以一个简单的场景为例,带大家一步步了解如何解决这个问题——既能高效利用缓存,又能......
  • 《深度学习》—— 神经网络模型对手写数字的识别
    文章目录一、数据集介绍二、神经网络模型对手写数字识别步骤和完整代码一、数据集介绍此模型训练的数据集是torchvision库中datasets数据包中的MNIST数据集MNIST数据集中含有70000张由不同的人手写数字图像,其中6000张训练集,1000张是测试集每张图片都是......
  • 如何手写一个SpringBoot框架
    在这篇文章中,我们将手写模拟SpringBoot的核心流程,让大家能够以一种简单的方式了解SpringBoot的大概工作原理。项目结构我们创建一个工程,包含两个模块:springboot模块,表示SpringBoot框架的源码实现。user包,表示用户业务系统,用来写业务代码来测试我们所模拟出来的SpringBoo......
  • 分布式事务一致性:本地消息表设计与实践
    概念本地消息表是一种常见的解决分布式事务问题的方法。其核心思想是将分布式事务拆分成本地事务来处理,通过消息队列来保证各个本地事务的最终一致性。实现步骤创建本地消息表:在数据库中创建一个本地消息表,用于存储待发送的消息以及消息的发送状态和相关信息。表结构通......
  • Redis面试题-如何保持缓存一致性
    1、延迟双删延迟双删策略是一种用于解决缓存与数据库之间数据一致性问题的方法。其基本思想是在更新数据库时,通过两次删除缓存的操作来尽可能地保证数据的一致性。具体步骤包括:首先,在更新数据库之前删除缓存;然后,执行数据库更新操作;最后,在延迟一段时间后再次删除缓存。优点:减......
  • 从架构到业务:实现企业一致性与合规性的价值流优化方案
    确保业务与架构的一致性与合规性——企业未来成功的关键随着企业数字化转型不断加速,如何在保持合规的同时确保业务架构与战略目标的高度一致,已经成为企业在当今市场竞争中的关键成功因素。为了在多变的商业环境中立足,企业不仅需要优化其业务流程,还必须确保企业架构与其战略目......
  • 手把手教你自己动手写cpu(六)--算术操作指令实现
    目录1.加法指令(Addition)实现思路Verilog实现示例2.减法指令(Subtraction)实现思路Verilog实现示例3.乘法指令(Multiplication)实现思路Verilog实现示例4.除法指令(Division)实现思路Verilog实现示例ALU模块乘法器模块除法器模块顶层模块测试模块总结 ......
  • 数据库数据恢复—Oracle数据库打开报错“system01.dbf需要更多的恢复来保持一致性,数据
    Oracle数据库故障&检测:打开oracle数据库报错“system01.dbf需要更多的恢复来保持一致性,数据库无法打开”。数据库没有备份,无法通过备份去恢复数据库。恢复zxfg用户下的数据。出现“system01.dbf需要更多的恢复来保持一致性”这个报错的原因可能是控制文件损坏、数据文件损坏,数据......