sharding-jdbc事务解读

2023-10-29

序言

sharding-jdbc在分库分表方面提供了很大的便利性,在使用DB的时候,通常都会涉及到事务这个概念,而在分库分表的环境上再加上事务,就会使事情变得复杂起来。本章试图剖析sharding-jdbc在事务方面的解决思路。

传统事务回顾

传统的事务模型如下:

Connection conn = getConnection();
try{
    Statement stmt1 = conn.parpareStatement(sql1);
    stmt1.executeUpdate();
    Statement stmt2 = conn.parpareStatement(sql2);
    stmt2.executeUpdate();
    conn.commit();
}catch(Exception e){
    conn.rollback();
}

对于同一个连接,可以执行多条sql语句,任何一条语句出现错误的时候,整个操作流程都可以回滚,从而达到事务的原子操作。

再来看最基本的spring事务操作:

class ServiceA(){
   public void updateA(){...}
}
class ServiceB(){
    public void updateB(){...}
}
@Transactional
class ServiceC(){
    public void updateC(){
        serviceA.updateA();
        serviceB.updateB();
    }
}

我们知道,当updateC执行的时候,不管是updateA还是updateB出现了异常,updateC都可以整体回滚,达到原子操作的效果,其主要原因是updateA和updateB共享了同一个Connection,这是spring底层通过ThreadLocal缓存了Connection实现的。

以上介绍的这两种情况都只是针对单库单表的原子操作,事务的实现并不难理解,那么在跨库的情况下,sharding-jdbc又是如何解决事务问题的呢?

shrading-jdbc之弱事务

在官方文档中,针对弱事务有如下三点说明:

  • 完全支持非跨库事务,例如:仅分表,或分库但是路由的结果在单库中。
  • 完全支持因逻辑异常导致的跨库事务。例如:同一事务中,跨两个库更新。更新完毕后,抛出空指针,则两个库的内容都能回滚。
  • 不支持因网络、硬件异常导致的跨库事务。例如:同一事务中,跨两个库更新,更新完毕后、未提交之前,第一个库死机,则只有第二个库数据提交。

为了理解以上几点,我们来看看sharding-jdbc默认是如何处理事务的。

这里写图片描述

这是一个非常常见的处理模式,一个总连接处理了多条sql语句,最后一次性提交整个事务,每一条sql语句可能会分为多条子sql分库分表去执行,这意味着底层可能会关联多个真正的数据库连接,我们先来看看如果一切正常,commit会如何去处理。

public abstract class AbstractConnectionAdapter extends AbstractUnsupportedOperationConnection {
    @Override
    public final void commit() throws SQLException {
        Collection<SQLException> exceptions = new LinkedList<>();
        for (Connection each : cachedConnections.values()) {
            try {
                each.commit();
            } catch (final SQLException ex) {
                exceptions.add(ex);
            }
        }
        throwSQLExceptionIfNecessary(exceptions);
    }
}

引擎会遍历底层所有真正的数据库连接,一个个进行commit操作,如果任何一个出现了异常,直接捕获异常,但是也只是捕获而已,然后接着下一个连接的commit,这也就很好的说明了,如果在执行任何一条sql语句出现了异常,整个操作是可以原子性回滚的,因为此时所有连接都不会执行commit,但如果已经到了commit这一步的话,如果有连接commit失败了,是不会影响到其他连接的。

sharding-jdbc之柔性事务

sharding-jdbc的弱事务并不是完美的,有时可能会导致数据的一致性问题,所以针对某些特定的场景,又提出了柔性事务的概念。先来看一张官方的说明图:

这里写图片描述

这里想表达两个意思:
1. 对于sql的执行,在执行前记录日志,如果执行成功,把日志删除,如果执行失败,重试一定次数(如果未达到最大尝试次数便执行成功了,一样删除日志)。
2. 异步任务不断扫描执行日志,如果重试次数未达到最大上限,尝试重新执行,如果执行成功,删除日志。

从上面两点分析可以看出,由于采用的是重试的模式,也就是说同一条语句,是有可能被多次执行的,所以官方提到了柔性事务的适用场景:

  • 根据主键删除数据。
  • 更新记录永久状态,如更新通知送达状态。

而且它还有一定的限制: SQL需要满足幂等性,具体为:

  • INSERT语句要求必须包含主键,且不能是自增主键。
  • UPDATE语句要求幂等,不能是UPDATE xxx SET x=x+1
  • DELETE语句无要求。

在有了一个大概的了解之后,我们来更加深入的了解。

sharding-jdbc使用了google的EventBus事件模型,注册了一个Listener,监听器对三种事件进行了处理,如下代码所示:

switch (event.getEventExecutionType()) {
            case BEFORE_EXECUTE:
                transactionLogStorage.add(new TransactionLog(event.getId(), bedSoftTransaction.getTransactionId(), bedSoftTransaction.getTransactionType(), 
                        event.getDataSource(), event.getSql(), event.getParameters(), System.currentTimeMillis(), 0));
                return;
            case EXECUTE_SUCCESS: 
                transactionLogStorage.remove(event.getId());
                return;
            case EXECUTE_FAILURE: 
                boolean deliverySuccess = false;
                for (int i = 0; i < transactionConfig.getSyncMaxDeliveryTryTimes(); i++) {
                    if (deliverySuccess) {
                        return;
                    }
                    boolean isNewConnection = false;
                    Connection conn = null;
                    PreparedStatement preparedStatement = null;
                    try {
                        conn = bedSoftTransaction.getConnection().getConnection(event.getDataSource(), SQLType.DML);
                        if (!isValidConnection(conn)) {
                            bedSoftTransaction.getConnection().release(conn);
                            conn = bedSoftTransaction.getConnection().getConnection(event.getDataSource(), SQLType.DML);
                            isNewConnection = true;
                        }
                        preparedStatement = conn.prepareStatement(event.getSql());
                        //TODO for batch event need split to 2-level records
                        for (int parameterIndex = 0; parameterIndex < event.getParameters().size(); parameterIndex++) {
                            preparedStatement.setObject(parameterIndex + 1, event.getParameters().get(parameterIndex));
                        }
                        preparedStatement.executeUpdate();
                        deliverySuccess = true;
                        transactionLogStorage.remove(event.getId());
                    } catch (final SQLException ex) {
                        log.error(String.format("Delivery times %s error, max try times is %s", i + 1, transactionConfig.getSyncMaxDeliveryTryTimes()), ex);
                    } finally {
                        close(isNewConnection, conn, preparedStatement);
                    }
                }
                return;
            default: 
                throw new UnsupportedOperationException(event.getEventExecutionType().toString());
        }

以上代码可以抽取为如下图的描述:
这里写图片描述

监听器根据三种不同的事件类型对事务日志进行不同的操作。有监听 ,必然就有事件的投递,那么引擎是什么时候产生这些事件的呢?
我们知道每一条sql语句拆分后有可能对应多条子sql语句,而每一条子sql语句是单独执行的,执行是封装在一个内部方法的:

private <T> T executeInternal(final SQLType sqlType, final BaseStatementUnit baseStatementUnit, final List<List<Object>> parameterSets, final ExecuteCallback<T> executeCallback, 
                          final boolean isExceptionThrown, final Map<String, Object> dataMap) throws Exception {
        synchronized (baseStatementUnit.getStatement().getConnection()) {
            T result;
            ExecutorExceptionHandler.setExceptionThrown(isExceptionThrown);
            ExecutorDataMap.setDataMap(dataMap);
            List<AbstractExecutionEvent> events = new LinkedList<>();
            if (parameterSets.isEmpty()) {
                events.add(getExecutionEvent(sqlType, baseStatementUnit, Collections.emptyList()));
            }
            for (List<Object> each : parameterSets) {
                events.add(getExecutionEvent(sqlType, baseStatementUnit, each));
            }
            for (AbstractExecutionEvent event : events) {
                EventBusInstance.getInstance().post(event);
            }
            try {
                result = executeCallback.execute(baseStatementUnit);
            } catch (final SQLException ex) {
                for (AbstractExecutionEvent each : events) {
                    each.setEventExecutionType(EventExecutionType.EXECUTE_FAILURE);
                    each.setException(Optional.of(ex));
                    EventBusInstance.getInstance().post(each);
                    ExecutorExceptionHandler.handleException(ex);
                }
                return null;
            }
            for (AbstractExecutionEvent each : events) {
                each.setEventExecutionType(EventExecutionType.EXECUTE_SUCCESS);
                EventBusInstance.getInstance().post(each);
            }
            return result;
        }
    }

以上代码可以简化为如下流程:

这里写图片描述

其实执行流程比较简单,但还有两个重要的细节这里没有体现:

  1. 当使用柔性事务的时候,需要创建事务管理器,并获取事务对象,调用事务对象的begin开始一个事务,在这一步,会强制设置连接的autoCommit=true,这会导致所有的sql语句执时后立即提交,想想如果能回滚,那柔性事务也就失去了意义。

  2. 当事务执行begin时,会标记当前不抛出异常,这样当执行sql语句有异常时,会生成相应的EXECUTE_FAILURE事件,从而进行事务日志处理,而不是往外抛出异常,当事务结束时,调用事务对象的end方法,恢复异常的捕获。

一个常见的代码编写模式如下(来自官方的demo)

private static void updateFailure(final DataSource dataSource) throws SQLException {
        String sql1 = "UPDATE t_order SET status='UPDATE_1' WHERE user_id=10 AND order_id=1000";
        String sql2 = "UPDATE t_order SET not_existed_column=1 WHERE user_id=1 AND order_id=?";
        String sql3 = "UPDATE t_order SET status='UPDATE_2' WHERE user_id=10 AND order_id=1000";
        SoftTransactionManager transactionManager = new SoftTransactionManager(getSoftTransactionConfiguration(dataSource));
        transactionManager.init();
        BEDSoftTransaction transaction = (BEDSoftTransaction) transactionManager.getTransaction(SoftTransactionType.BestEffortsDelivery);
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            transaction.begin(conn);
            PreparedStatement preparedStatement1 = conn.prepareStatement(sql1);
            PreparedStatement preparedStatement2 = conn.prepareStatement(sql2);
            preparedStatement2.setObject(1, 1000);
            PreparedStatement preparedStatement3 = conn.prepareStatement(sql3);
            preparedStatement1.executeUpdate();
            preparedStatement2.executeUpdate();
            preparedStatement3.executeUpdate();
        } finally {
            transaction.end();
            if (conn != null) {
                conn.close();
            }
        }
    }

看到这个编写模式,你一定会想,如果我使用MyBatis和spring,这一切能否整合起来,这个话题有兴趣大家可以去尝试。

总结

分布式事务处理起来有一定的难度,sharding-jdbc采用了简单的弱事务模式和特殊场景下的柔性事务模式,没有最好,只有更好,根据自身业务去选择事务模式才是最重要的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

sharding-jdbc事务解读 的相关文章

  • ShardingJDBC核心概念与快速实战

    目录 ShardingSphere介绍 ShardingSphere特点 ShardingSphere简述 ShardingSphere产品区分 ShardingJDBC实战 核心概念 实战 ShardingJDBC的分片算法 Shardi
  • 一个99%的人都说不清楚知识点——Spring 事务传播行为

    面试过很多人 大部分都能把事务的四个特性及隔离级别说得七七八八 但当问到 Spring 的传播行为时 就基本上没人能说出个一二三了 我们都知道 一个事务要么成功 要么失败 但当若干个事务配合完成一个复杂任务时 就不能简单的这样一刀切了 我们
  • sharding-jdbc事务解读

    序言 sharding jdbc在分库分表方面提供了很大的便利性 在使用DB的时候 通常都会涉及到事务这个概念 而在分库分表的环境上再加上事务 就会使事情变得复杂起来 本章试图剖析sharding jdbc在事务方面的解决思路 传统事务回顾
  • 数据库的事务

    以MySQL为视角 了解数据库的事务 目录 一 事务简介 1 概念 2 操作 3 例子 4 事务提交方式 二 事务的四大特征 ACID 1 原子性 atomicity 2 一致性 consistency 3 隔离性 isolation 4
  • Sharding-jdbc踩坑记录(一)

    sharding jdbc学习链接 sharding jdbc版本 2 0 3 springboot版本 2 1 5 连接池 druid 1 1 14
  • 【Redis】深入理解 Redis 事务机制

    文章目录 前言 一 回顾 MySQL 事务 1 1 MySQL 事务的概念与特性 1 1 MySQL 事务的管理 二 对 Redis 事务的认识 2 1 什么是 Redis 的事务 2 1 1 Redis 事务的概念 2 1 2 对 Red
  • 一个轻量级的java jdbc,SQL模式,支持多数据源,jdbc-0.0.5

    运行条件 JDK 1 7 特性 轻量 高效 无复杂配置 直接手写SQL 可配置多数据源 支持多数据源事务 支持返回多结果集 使用方式
  • 解决用C#操作mysql数据库出错无法回滚的问题

    mysql同时执行多条命令 部分成功部分失败 返回的结果是操作失败 但是成功的部分不会自动回滚 网上搜到解决方法 可以判断返回值 只要有一条语句操作失败返回值就会是0 此时调用回滚 相关代码有很多 可以百度到 我一开始按照上述流程始终无法回
  • 记录:Sharding-Jdbc 配置max.connections.size.per.query造成的死锁问题

    记录 Sharding Jdbc 配置max connections size per query造成的死锁问题 项目场景 版本 jdk11 sharding jdbc4 1 1 mysql8 0 分表 table表根据 主键id 水平分表
  • 【spring】spring 的事务(transaction) 三 try catch对事务的影响

    文章目录 概述 1 非异常用例 1 1 创建工程 1 2 执行 2 内层抛出非check异常 外层进行捕获 3 内层抛出非check异常 外层不进行捕获 相关文章 spring 的事务 transaction 一 基础概念介绍 spring
  • redis 事务实现原理

    一 简介 Redis事务通常会使用MULTI EXEC WATCH等命令来完成 redis实现事务实现的机制与常见的关系型数据库有很大的却别 比如redis的事务不支持回滚 事务执行时会阻塞其它客户端的请求执行 二 事务实现细节 redis
  • Java多线程-并行处理以及事务控制

    1 为了提高我们接口的响应速度 我们可以开多个线程 并行处理 比如我们要大批量写入数据到数据库 Autowired private ThreadPoolExecutor executor Autowired private Platform
  • 简说数据库事务的ACID

    事务是应用程序中一系列严密的操作 所有操作必须成功完成 否则在每个操作中所作的所有更改都会被撤消 也就是事务具有原子性 一个事务中的一系列的操作要么全部成功 要么一个都不做 原子性 Atomicity 一致性 Consistency 隔离性
  • 数据库报Connection is read-only. Queries leading to data modification are not allowed

    数据库报Connection is read only Queries leading to data modification are not allowed 具体是某张表的插入操作时报的错误 问题排查过程 这个超过是批量操作发生的 第一
  • Springboot2.X 集成 Sharding-JDBC3.X

    之前的项目中集成了springboot2 x dangdang sharding jdbc 1 5 4 1 pom依赖
  • 如何在数据库事务提交成功后进行异步操作

    原文链接 问题 业务场景 业务需求上经常会有一些边缘操作 比如主流程操作A 用户报名课程操作入库 边缘操作B 发送邮件或短信通知 业务要求 操作A操作数据库失败后 事务回滚 那么操作B不能执行 失败后也可以重新进行自调度 操作A执行成功后
  • 深入理解Spring的事务传播行为

    前言 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为 事务传播行为是Spring框架独有的事务增强特性 它不属于的事务实际提供方数据库行为 这是Spring为我们提供的强大的工具箱 使用事务传播行
  • SpringBoot事务管理-5个面试核心类源码刨析

    简单的事重复做 你就是专家 重复的事用心做 你就是赢家 在开始讲解SpringBoot事务之前 我们先来整体回顾下事务的概念及特性 便于我们了解SpringBoot是如何解决事务相关问题的 另外这部分也是面试必考内容 需要学习交流的可入群
  • 事务,不只ACID

    1 什么是事务 应用在运行时可能会发生数据库 硬件的故障 应用与数据库的网络连接断开或多个客户端端并发修改数据导致预期之外的数据覆盖问题 为了提高应用的可靠性和数据的一致性 事务应运而生 从概念上讲 事务是应用程序将多个读写操作组合成一个逻
  • spring的事务配置详解

    接下来我将给大家介绍spring事务配置的两种方式 1 基于XML的事务配置 2 基于注解方式的事务配置 前言 在我们详细介绍spring的两种声明式事务管理之前 我们需要先理解这些概念 1 spring的事务管理是通过Aop的方式来实现

随机推荐

  • Python re模块 findall()函数(算是一个坑点)

    1 先说一下findall 函数的两种表示形式 import re kk re compile r d kk findall one1two2three3four4 1 2 3 4 注意此处findall 的用法 可传两个参数 kk re
  • QT框架简单分析

    基本带窗口创建的项目具备以下几个文件 pro 工程管理文件 用于管理各种模块 名字 连接等 在后续添加 通讯模块 多媒体 数据库等模块时需要用到 头文件 各种 h结尾的头文件 如widget h 源文件 主函数main cpp 和函数文件如
  • 在Python中,我们可以使用pyzmq模块来实现ZMQ编程

    随着现代计算机技术的不断进步 人们对于通信技术的要求越来越高 而在这个领域中 ZeroMQ 简称ZMQ 被誉为是最为出色的一个库之一 它的出色之处在于 它为我们提供了一套非常灵活 高效的通信协议 可以让我们在不同的网络环境下 以各种方式进行
  • 0长度char数组的使用

    需要引起注意的 ISO IEC 9899 1999里面 这么写是非法的 这个仅仅是GNU C的扩展 gcc可以允许这一语法现象的存在 结构体最后使用0或1的长度数组的原因 主要是为了方便的管理内存缓冲区 如果你直接使用指针而不使用数组 那么
  • 彻底关闭Windows Defender&Windows 更新

    目录 关闭Windows Defender 方法一 方法二 关闭Windows Updata 自测用这个方法关闭 还是莫名其妙会被杀 在加一个办法 关闭Windows Defender 方法一 1 使用WIN R打开运行对话框 输入rege
  • C++:给定一个字符串,验证是否为回文,只考虑字母和数字字符,忽略字母大小写。

    include
  • 【微信小程序】微信小程序实现点击分享链接进入的分享页面左上角是返回按钮

    首先先和你们说这是可以实现而且非常简单 接下来我们就来看看如何实现这种需求的 首先我们需要配置分享链接 例如 detail js页面 Page onShareAppMessage function res var url 页面参数 if r
  • Spring中的18个注解,你会几个?

    点击上方 Java之间 选择 置顶或者星标 你关注的就是我关心的 作者 Java的小本家 Controller 标识一个该类是Spring MVC controller处理器 用来创建处理http请求的对象 RestController S
  • 结合AG-Grid二次封装element-plus的el-table表格

    MyTable组件封装 路径 conponents MyTable index vue template
  • 第三周课程总结&实验报告一

    一 实验报告 1 打印输出所有的 水仙花数 所谓 水仙花数 是指一个3位数 其中各位数字立方和等于该数本身 例如 153是一个 水仙花数 I 实验代码 public class text public static void main St
  • 操作系统复习题

    一 填空题 1 通常所说操作系统的四大模块是指处理机管理 存储管理 设备管理 文件 管理 2 进程实体是由 进程控制块 PCB 程序段和数据段这三部分组成 3 文件系统中 空闲存储空间的管理方法有空闲表法 空闲链表法 位示图法和 成组链接法
  • 7_2,24位真彩模式(2013-2-27)

    同理 24位为3通道 3字节 但是经过测试 有问题 不支持24位 7 3 32位真彩模式 32位与16位不同之处 32位分为ARGB和XRGB 各8位 ARGB中前8位为透明色 XRGB前8位为了对齐 一般清为0 define RGB32B
  • JDBC流程

    JDBC JAVA 访问数据库的技术 Jdbc是一种Java连接数据库技术 Java database connectity 它是 Java 提供的一些接口 这些接口大部分是数据库厂商提供的 jar包 我们要做的 是连接数据库以后 如何使用
  • php和nginx镜像合并 && 代码打包到镜像 —— k8s从入门到高并发系列教程 (二)

    上文使用了nginx和php fpm两个镜像组装了nginx php环境 然而实际企业的微服务架构 nginx和php fpm是被统一看作一个微服务供其他服务调用的 另外 配置文件和源代码也不会通过映射到容器中的方式进行 而是打包到了企业的
  • pytorch中dataloader的num_workers参数

    结论速递 在Windows系统中 num workers参数建议设为0 在Linux系统则不需担心 1 问题描述 在之前的任务超大图上的节点表征学习中 使用PyG库用于数据加载的DataLoader ClusterLoader等类时 会涉及
  • [分享]我发现了一个快速完成物联网毕业设计的好方法!

    对于计算机相关专业的毕业生来说 毕业论文真的是一件特别令人头疼的事情 当然学霸除外 毕设 编程 每每想到这里 是不是很想原地爆炸 莫着急往下看 确认过眼神 你就是我要帮助的人 下面就给大家介绍一个快速完成毕业设计的方法 绝对的亲身实践哦 作
  • [极客大挑战 2019]Secret File

    进入靶场查看源码 进入这个网页 5f5574c7 75b8 4251 befa d89a391dafc4 node4 buuoj cn 81 Archive room php 点击这个就会跳入下一个网页 发现没有东西 抓包试试 先返回到这个
  • vite项目集成eslint和prettier

    一 eslint介绍 eslint中文官网 https zh hans eslint org docs latest use getting started 1 什么是eslint ESLint是一个开源的JavaScript代码静态分析工
  • vscode连接远程Linux服务器

    文章目录 一 环境安装 1 1 下载vscode 1 2 下载vscode sever 二 ssh链接 2 1 安装Remote SSH 2 2 设置vscode ssh 2 3 设置免密登录 2 3 1 本地生成公私钥 2 3 2 服务器
  • sharding-jdbc事务解读

    序言 sharding jdbc在分库分表方面提供了很大的便利性 在使用DB的时候 通常都会涉及到事务这个概念 而在分库分表的环境上再加上事务 就会使事情变得复杂起来 本章试图剖析sharding jdbc在事务方面的解决思路 传统事务回顾