实战|记一次MySQL亿级数据的平滑迁移

亿级数据分库分表实战:ShardingSphere的平滑迁移方案 《亿级数据下ShardingSphere平滑迁移方案》摘要:随着数据量突破亿级,传统数据库架构面临性能瓶颈。Apache ShardingSphere作为分布式数据库中间件,通过分库分表解决存储与处理能力问题。文章详细解析ShardingSphere核心架构,包括分片流程、策略算法,并提出分库分表前的规划设计标准。重点介绍"双集群切换"平滑迁移方案,涵盖环境准备、规则配置、实战步骤及监控方法。同时探讨迁移中的关键问题如数据一致性、业务过渡、分布式ID生成等解决方案,最后给出迁移后的运维优 阅读详情

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

1c7ca382a491ebb9ed4ca171bca25b72.gif

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号、CRM 等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro

  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud

  • 视频教程:https://doc.iocoder.cn

【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本 

来源:vivo互联网技术


一、背景

预约业务是 vivo 游戏中心的重要业务之一。由于历史原因,预约业务数据表与其他业务数据表存储在同一个数据库中。当其他业务出现慢 SQL 等异常情况时,可能会直接影响到预约业务,从而降低系统整体的可靠性和稳定性。为了尽可能提高系统的稳定性和数据隔离性,我们迫切需要将预约相关数据表从原来的数据库中迁移出来,单独建立一个预约业务的数据库。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

二、方案选型

常见的迁移方案大致可以分为以下几类:

b5493207a5e877570aaf47b2463bb472.png

而预约业务有以下特点:

  • 读写场景多,频率高,在用户预约/取消预约/福利发放等场景均涉及到大量的读写。

  • 不可接受停机,停机不可避免的会造成经济损失,在有其他方案的情况下不适合选择此方案。

  • 大部分的场景能接受秒级的数据不一致,少部分不能。

结合这些特点,我们再评估下上面的方案:

8be7197970af5b6105327cea83a4fbce.png

停机迁移方案需要停机,不适用于预约场景。预约场景存在不活跃的用户数据,如果用渐进式迁移方案的话很难迁移干净,可能还需要再写一个迁移任务来辅助完成迁移。而双写方案最大的优势在于每一步操作都可向上回滚,能尽可能的保证业务不出问题。

因此,最终选择的是双写方案。预约业务涉及到的读写场景多,每一个场景单独进行改造的成本大,采用 Mybatis 插件来实现迁移所需的双写等功能,可以有效降低改造成本。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

三、前期准备

3.1 全量同步&增量同步&一致性校验

这几步使用了公司提供的数据同步工具。全量同步基于 MySQLDump 实现;增量同步基于 binlog 实现;一致性校验通过在新老库各选一个分块,然后聚合列数据计算并对比其特征值实现。

3.2 代码改造

引入了新库,那自然就需要在项目里新建数据源,并创建表对应的 Mybatis Mapper 类。这里有一个小细节需要注意,Mybatis 默认的 BeanNameGenerator 是

AnnotationBeanNameGenerator,它会使用类名作为 BeanName 注册到 Spring 的 ioc 容器中,Spring 启动时如果发现有了两个重名 Bean 就会启动失败,笔者这里给 Mybatis 设置了一个新的 BeanNameGenerator ,使用类的全路径名作为 BeanName 解决了问题。

public class FullPathBeanNameGenerator implements BeanNameGenerator {
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        return definition.getBeanClassName();
    }
}

还有一点是主键 id,本次预约迁移需要保证新老库主键 id 一致,预约业务没做分库分表,id 都是直接用 MySQL 的自增 id,没有用 id 生成器之类的中间件。因此插入新表时只需要使用插入老表后 Mybatis 自动设置好的 id 即可,这次迁移前先检查了一遍业务代码,确保插入语句都用了 Mybatis 的 useGeneratedKeys 功能来自动设置 id。

3.3 插件实现

Mybatis 插件可以拦截 SQL 语句执行过程中的某一点进行干预和处理,而 Executor 是 Mybatis 中负责执行 SQL 语句的核心组件。我们可以对 Executor 的 update 和 query 方法进行代理以实现迁移所需的功能。

插件需要为读写场景分别实现以下功能:

c52dea3a8528e39766c2fd6446959e6d.png

考虑到开关切换部分的代码逻辑较为简单,因此在下文中,笔者将不再过多介绍该部分的具体实现,而是着重介绍如何在插件中使用老库的执行语句来访问新的数据库。此外,代码里会涉及到 Mybatis 相关的一些概念,由于网上已经有较多详尽的资料,这里就不再赘述。

迁移插件代理了 Executor 的 query 和 update 方法,首先在插件里获取到当前执行的 SQL 语句所在的 Mapper 路径。

@Intercepts(
        {
                @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
public class AppointMigrateInterceptor implements Interceptor {
 
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
 
        Object[] args = invocation.getArgs();
        // Mybatis插件代理的Executor的update或者query方法,第一个参数就是MappedStatement
        MappedStatement ms = (MappedStatement) args[0];
        SqlCommandType sqlCommandType = ms.getSqlCommandType();
        String id = ms.getId();
        // 从MappedStatement id中获取对应的Mapper接口文件全路径
        String sourceMapper = id.substring(0, id.lastIndexOf("."));
 
        // ...
    }
     
    // ...
}

得到老库 Mapper 路径后,将其转换为新库 Mapper 路径,再使用 Class.forName 获取到新库 Mapper 类,然后用新库的 sqlSessionFactory 开启 sqlSession,再获取反射调用所需的方法、对象、参数,在新库上执行语句。

protected Object invoke(Invocation invocation, TableConfiguration tableConfiguration) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    // 获取 MappedStatement
    MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
 
    // 获取 Mybatis 封装好的入参,封装函数 MapperMethod.convertArgsToSqlCommandParam(Object[] args)
    Object parameter = invocation.getArgs()[1];
 
    // 使用 Class.forName 获取到的新库 Mapper
    Class<?> targetMapperClass = tableConfiguration.getTargetMapperClazz();
 
    // 使用新库的 sqlSessionFactory 创建 sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    Object result = null;
    try{
        // 使用新库的 Mapper 路径获取对应的 MapperProxy 对象
        Object mapper = sqlSession.getMapper(targetMapperClass);
 
        // 将 Mybatis 封装好的参数转换为原始参数
        Object[] paramValues = getParamValue(parameter);
 
        // 使用 mappedStatement Id 从新库对应的 Mapper 里获取对应的方法
        Method method = getMethod(ms.getId(), targetMapperClass, paramValues);
        paramValues = fixNullParam(method, paramValues);
 
        // 反射调用新库 Mapper 的方法,本质上执行的是 MapperProxy.invoke
        result = method.invoke(mapper, paramValues);
    } finally {
        sqlSession.close();
    }
    return result;
}
 
private Object[] fixNullParam(Method method, Object[] paramValues) {
    if (method.getParameterTypes().length > 0 && paramValues.length == 0) {
        return new Object[]{null};
    }
    return paramValues;
}

上述代码里,getMethod 方法负责从新库 Mapper 类里找到对应的方法,以用于后续的反射调用。

private Method getMethod(String id, Class mapperClass) throws NoSuchMethodException {
    //获取参数对应的 class
    String methodName = id.substring(id.lastIndexOf(".") + 1);
    String key = id;
    // methodCache 用来缓存 MappedStatement 和对应的 Method,避免每次都从 Mapper 里查找
    Method method = methodCache.get(key);
    if (method == null){
        method = findMethodByMethodSignature(mapperClass, methodName);
        if (method == null){
            throw new NoSuchMethodException("No such method " + methodName + " in class " + mapperClass.getName());
        }
        methodCache.put(key,method);
    }
    return method;
}
 
private Method findMethodByMethodSignature(Class mapperClass,String methodName) throws NoSuchMethodException {
    // mybatis 的 Mapper 内的方法不支持重载,所以这里只要方法名匹配到了就行,不用进行参数的匹配
    Method method = null;
    for (Method m : mapperClass.getMethods()) {
        if (m.getName().equals(methodName)) {
            method = m;
            break;
        }
    }
    return method;
}

得到方法后,还需要得到反射调用所需的参数。Mybatis 执行到 Executor.update/query 方法时,参数已经经过  MapperMethod.convertArgsToSqlCommandParam(Object[] args) 方法封装,不能直接用来执行 MapperProxy.invoke ,需要转换后才可用。下图是MapperMethod.convertArgsToSqlCommandParam(Object[] args) 的封装过程,而下面的 getParamValue 是这个函数的逆过程。

059518a4b3c7e7e1b7d617eb23d848f0.png
private Object[] getParamValue(Object parameter) {
    List<Object> paramValues = new ArrayList<>();
 
    if (parameter instanceof Map) {
        Map<String, Object> paramMap = (Map<String, Object>) parameter;
        if (paramMap.containsKey("collection")) {
            paramValues.add(paramMap.get("collection"));
        } else if (paramMap.containsKey("array")) {
            paramValues.add(paramMap.get("array"));
        } else {
            int count = 1;
            while (count <= paramMap.size() / 2){
                try {
                    paramValues.add(paramMap.get("param"+(count++)));
                }catch (BindingException e){
                    break;
                }
            }
        }
    } else if (parameter != null){
        paramValues.add(parameter);
    }
    return paramValues.toArray();
}

(左右滑动查看更多)

通过上述流程,我们就能使用 Mybatis 插件拦截老库的执行过程,实现迁移所需的读写数据源切换/新老库查询结果对比/先写老库再异步写新库等功能。

四、双写流程

4.1 上线双写改造后的业务代码,上线时只读写老库

  1. 读开关:只读老库

  2. 写开关:只写老库

  3. 新老库查询结果对比开关:关

此时业务仍只读写老库。

4.2 使用公司中间件平台提供的数据工具同步老库数据到新库

  1. 读开关:只读老库

  2. 写开关:只写老库

  3. 新老库查询结果对比开关:关

第1步和第2步并没有严格的顺序要求,只要在切换为双写前做完第1步和第2步就好。

条件允许的情况下,全量+增量同步时应选择不对外提供服务的离线从库作为数据源,避免主从延迟等问题对线上业务造成影响。

4.3 停止同步程序,然后开启双写

  1. 读开关:只读老库(开启查询结果对比开关)

  2. 写开关:双写

  3. 新老库查询结果对比开关:开

老库追上新库后,对数据做一次全量校验,避免出现数据不一致的情况。此外还需要开启新老库查询结果对比开关,通过日志监控观察新老库的查询结果是否一致。

停止数据同步和切换双写之间必然有时间差,如果先开启双写再停止数据同步,则可能出现插入重复数据或数据被覆盖的情况。因此需要对数据同步工具和迁移插件进行改造,以处理数据异常的情况,但是这样改造需要处理的情况较多,改造成本较高。所以这里选择先停止同步,再切换到双写,中间丢失的数据使用对比&补偿任务恢复,由于此时仍然全量读老库,所以对业务不会有影响。需要注意的是,双写阶段的时间不应太长,只要确保新老库数据一致就应该前进到下一步。

这一步在实际操作过程中需要注意以下情况:

4.3.1 自增主键

预约业务新库的主键 id 需要和老库保持一致,因此在迁移前检查了一遍业务代码,确保插入语句都用了 Mybatis 的 useGeneratedKeys 功能来返回 id ,这样插入新库时可以直接用设置好 id 的对象。但是这里有一个问题,批量插入时 Mybatis 自动设置的 id 和数据库生成的自增主键不一定完全一致,比如批量 insert ignore 和 on duplicate key update 语句。

这个问题和 useGeneratedKeys 的实现有关,代码可参考

com.mysql.jdbc.StatementImpl#getGeneratedKeysInternal(long) 函数,以下是其执行逻辑:

  1. Mybatis 执行完插入语句后,MySQL 会返回这次插入影响的数据行数,注意,使用 insert ignore 插入时,忽略的那部分数据不会加到影响的行数上。

  2. Mybatis 使用 SELECT LAST_INSERT_ID() 查询这次插入的最小 id 。

  3. Mybatis 循环遍历插入时用的对象列表,循环的最大次数为第1步里获取的这次插入影响的行数,使用 n 代表当前的循环次数,列表中的每个对象的 id 被赋值为 LAST_INSERT_ID() + n*AUTO_INCREMENT 。

举例来说,假设老库的某张表里有数据 b ,其 id=1,此时往该表使用 insert ignore 批量插入三条数据 a,b,c,其在表内的 id 为 a:2、b:1、c:3,返回的影响行数为2,SELECT LAST_INSERT_ID() 返回的是2,因此 Mybatis 往对象里设置的主键分别为 a:2、b:3、c:null,再使用这个设置好 id 的对象列表插入新库时会导致新老库 id 不一致。

解决方案:由于直接删除 ignore 会改变这条 SQL 的语义,无法通过修改语句来解决问题。所以我们只能在迁移插件里跳过这条语句,使其固定写入老库。然后在业务层单独对其进行迁移改造,将插入新库的流程修改为先使用 id 以外的唯一键查询一次老库的数据,获取到 id 以后设置到对象列表里,再插入新库。

4.3.2 事务

预约业务有部分逻辑用到了事务,但这部分逻辑在双写期间均可以暂停功能,因此迁移插件没有实现事务的支持。如果需要支持业务的话可以不依赖插件,在业务层单独对那部分代码进行改造。

4.3.3 异步写入新库引起的问题

双写过程中是异步写新库,需要重点关注是否会有线程安全问题。举例来说,假设有个业务需要往表里插入一个列表,插入完列表后又对列表进行了修改,比如执行了 List.clear() 函数或者其中的对象发生了变更,由于是异步写新库,所以实际的执行流程可能如下:

  1. 老库 insert(list)

  2. list.clear()

  3. 新库 insert(list)

这会导致新库执行操作时,传入的对象和老库执行操作时不一样,导致新老库数据不一致。建议在迁移前人为的确认业务逻辑,避免异步写入导致新老库数据不一致。

4.4 开启对比和补偿程序,补偿切换开关的过程中遗失的数据

  1. 读开关:只读老库(对比开关开启)

  2. 写开关:双写

  3. 新老库查询结果对比开关:开

  4. 对比&补偿任务:开启

94dcf0caf4ca369fb4a6290c242a4cdc.png

该对比&补偿任务有一个缺陷,其不能处理数据被删除的情况,如果老库里的数据被删除但是新库的数据删除失败,那使用更新时间区间就无法从老库查出这条数据,自然也无法进行对比&补偿。

双写期间,如果出现删老库成功但是删新库失败的情况会有日志告警,所以不会有问题。但是停止数据同步工具 → 开启双写开关这一过程中删除的数据无法补偿。不过大部分业务用的都是逻辑删除,只有一处用了物理删除,笔者在这一处添加了日志,如果切换过程中出现删除数据的日志,就需要手动进行补偿操作。实际操作过程中,开关的切换的耗时较短,只花了30秒左右,在这过程中没有打印删除数据的日志。

4.5 逐步切量请求到新库上

  1. 读开关:部分读新库 → 只读新库

  2. 写开关:双写

  3. 新老库查询结果对比开关:开

  4. 对比&补偿任务:开启

双写时,由于数据先写入老库再异步写入新库,因此新库的数据肯定会滞后于老库。如果将一部分读流量切换到新库上,就可能会在一些对延迟要求较高的业务场景中出现问题。对于这种场景,我们不能采用逐步切量的策略,只能同时切换读写开关,将其修改为只写老库+只读新库。

4.6 停止对比补偿程序,关闭双写,读写都切换到新库,开启反向补偿任务

  1. 读开关:只读新库

  2. 写开关:只写新库

  3. 新老库查询结果对比开关:关

  4. 对比&补偿任务:开启反向补偿

反向补偿是从新库补偿数据到老库,由于该任务是定时执行,开启后,新库和老库的数据会有 1~2 分钟的延迟,万一写新库的逻辑有问题,可以切回老库。至于为什么用反向补偿任务而不是使用先写新库再异步写老库的策略,是因为双写是用 MyBatis 插件实现的,插件代理的是 excutor 的 update 和 query 方法,如果异步写入老库,有可能会发生以下情况:

  1. 假设有两个线程,业务线程 A 需要写入一条数据,迁移插件拦截后,先同步写入新库,写完新库后提交任务给线程 B 中异步写入老库,提交完任务后插件立刻返回。

  2. 由于插件已返回结果,executor 上层的 sqlsession 调用 close() 方法关闭 executor (见 org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke ),此时线程 B 可能还没执行完写老库的操作。

  3. 线程 B 执行过程中,由于 executor 已经关闭,导致其写老库失败。

因此无法使用 Mybatis 插件来实现异步写老库。

4.7 停止反向补偿任务,删除表迁移相关代码

停止反向补偿前,需要关注是否还有业务在读老库。观察一段时间,确认老库没有补偿任务以外的读写流量后,可以关闭补偿任务,清理迁移过程中产生的代码,清理老库数据。

五、总结

在进行数据表迁移的过程中,虽然遇到了一些问题,但是制定的方案中每一步都有回退措施,即使出现问题也不会影响业务的正常运行。此外,笔者在迁移过程中对各种异常情况进行了监控,能及时发现并解决问题。如果其他业务需要进行类似的迁移,需要关注以下几个方面:

  1. 迁移插件实现:在对迁移过程进行反思后,笔者人为通过代理或重写 MapperProxy 的方式来实现迁移插件可能是更加合理的方案。这种方案有两个优点:一方面,可以避免处理 Mybatis 复杂的参数转换流程,从而减少潜在的错误和异常;另一方面,可以实现先写新库再异步写老库的操作。但是这个方案没有经过实践,还不能确定是否有可行性。

  2. 自增主键:需要确定业务是否需要保证新老库的 id 一致。

  3. 事务:双写过程中应该结合业务考虑是否需要实现事务支持。本次迁移过程中,我们暂停了部分需要事务支持的业务。

  4. 异步写入:先写老库再异步写入新库的方式可能导致新老库数据不一致,迁移插件自身无法解决这个问题,只能人工提前排查可能存在的隐患。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,“长按”或“扫描”下方二维码噢:

01ffce6ab95dbeee5eab6a95581b2e6b.png

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

03d6295a70e693f7ece3f241130e5f4b.png

71a906178abb427c53d37e3922e6cf12.png8572413a95d9ff476fcd6419168ccbf1.png71ddb01e527abc8b51717b1bc4f7c2df.png19a74538397f1b1dba4329767d502bc7.png

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
使用OGG实现Oracle到MySQL数据平滑迁移 本文目录: 一、OGG概述 (一)OGG逻辑架构 二、迁移方案 (一)环境信息 (二)表结构迁移 (三)数据迁移 1.源端OGG配置 (1)Oracle数据库配置 (2)Oracle数据库OGG用户创建 (3)源端OGG 管理进程(MGR)配置 (4)源端OGG 表级补全日志(trandata)配置 (5)源端OGG 抽取进程(extract)配置 (6)源端OGG 传输进程(pump)配置 (7... 阅读详情

相关推荐

MySQL数据库迁移:实现数据平滑迁移的方法与示例代码

数据库迁移是一项复杂的任务,但通过合理的规划和正确的方法,我们可以实现平滑的数据迁移。本文介绍了导出源数据、导入数据到目标数据库以及使用数据库复制实现数据同步的方法,并提供了相应的示例代码。在实际的数据库迁移过程中,根据具体情况和需求,可能还需要考虑其他因素,例如数据转换和验证等。我们首先需要将源数据库中的数据导出,以便在目标数据库中导入。其次,创建目标数据库,并确保目标数据库的结构与源数据库一致。执行以上命令后,将会生成一个名为"数据导出文件.sql"的文件,其中包含了源数据库中的数据。

BitCodeW的博客 527

数据迁移:如何保证在不停机的情况下平滑的迁移数据

数据迁移的注意事项

码上一元 2204

计算机模拟爆破过程,基于LSDYNA岩石爆破模拟建模分析

原标题:基于LSDYNA岩石爆破模拟建模分析 Ls Dyna岩石爆破模拟仿真作者:dyna_focus擅长领域:dyna/abaqus/hypermesh一、 数值模型的建立1.1 单元及算法的选择岩石,炸药,空气都采用solid164实体单元。岩石采用常应力实体单元,该种单元属于纯粹的lagrange算法,该种算法单元网格附着在材料上,随着材料的流动而产生单元网格的变形,但是在结构变形巨大时,有...

weixin_29494379的博客 7316

MySQL 亿级数据平滑迁移实战

本文介绍了一次 MySQL 数据迁移的流程,通过方案选型、业务改造、双写迁移最终实现了亿级数据的迁移。

vivo互联网技术 1401

一篇讲透:豆包、元宝、DeepSeek、Kimi、WorkBuddy,职场里到底怎么分工

这篇文章深入分析了当前主流AI工具(WorkBuddy、豆包、腾讯元宝、DeepSeek、Kimi)的特点和使用场景,帮助读者根据不同工作需求选择合适的AI助手。核心观点包括: AI工具各有所长:豆包适合快速创作初稿,元宝擅长信息检索整理,DeepSeek强于逻辑分析,Kimi专精文本处理,WorkBuddy则更适合任务推进和成果交付。 使用前需明确需求:是要创意发散、信息整理,还是任务执行?不同阶段应选用不同工具组合。 提供了6个具体职场场景的解决方案,包括文章创作、课程设计、会议纪要等,并给出详细的操作

weixin_39284145的博客 1万+

关于Nuttx操作系统

NuttX是一个成熟的实时操作系统

qq_28576837的博客 4092

数据量超过亿级别,MySQL大表迁移该如何做?

NineData 提供的高效、快速、稳定的 MySQL 大表迁移能力,很好的补充了传统迁移方案的不足。当前,NineData 已经支持数十种常见数据库的迁移复制,同时,除了 SAAS 模式外,还提供了企业专属集群模式,满足企业最高的数据安全合规要求。目前,NineData 已在运营商、金融、制造业、地产、电商等多个行业完成大规模应用实践。如果您感兴趣的话,可以登录官网,立即开始免费使用。

云原生智能数据管理平台 4933

【NLP】语言模型的发展历程 (1)

N-gram模型是一种基于统计语言模型的文本分析算法,它用于预测文本中下一个词出现的概率,基于前面出现的n-1个词的序列。这里的n代表序列中元素的数量,因此称为N-gram。Unigram:N=1,每个单词的出现概率独立计算,不考虑上下文。Bigram:N=2,基于前一个单词预测当前单词的联合概率模型。Trigram:N=3,考虑前两个单词来预测当前单词的联合概率模型,更复杂但可能更准确。

酒酿小圆子呀~ 1668

MySQL亿级数据平滑迁移双写方案

由于该任务是定时执行,开启后,新库和老库的数据会有 1~2 分钟的延迟,万一写新库的逻辑有问题,可以切回老库。该对比&补偿任务有一个缺陷,其不能处理数据被删除的情况,如果老库里的数据被删除但是新库的数据删除失败,那使用更新时间区间就无法从老库查出这条数据,自然也无法进行对比&补偿。双写时,由于数据先写入老库再异步写入新库,因此新库的数据肯定会滞后于老库。假设有两个线程,业务线程 A 需要写入一条数据,迁移插件拦截后,先同步写入新库,写完新库后提交任务给线程 B 中异步写入老库,提交完任务后插件立刻返回。

Winn~ 1067

地心地固坐标系与北天东坐标系的转换

经过这一步旋转,x3轴和y3轴就处在过O3点的经度线所在的平面了,而且z3轴指向了东。将图3的坐标系旋转成图2的样子。这里需要多次旋转,每次以某个轴为对称轴进行旋转,对应的坐标变换参考。的原点在地心,Z轴指向北极点,X轴指向本初子午线与赤道的交点,Y轴指向符合右手定则,如下图所示。下面实现地心地固坐标系到北天东坐标系的转换,问题描述为:设一点。以y1轴为对称轴,顺时针旋转90°,得到图4的样子。,X轴指向北,Y轴指向天,Z轴指向东,如下图所示。因此,北天东坐标下的坐标为。点在图3坐标系下的坐标。

csdnqixiaoxin的博客 3556

3DMax马克杯制作教程

01 在前视图创建一个【圆柱体】,设置参数,长度为32,高度为82;高度分段12、边数为16;如图所示。 02 点击鼠标右键,把圆柱体转换为可编辑多边形,如图所示。 03 在修改面板中,选择【多边形】层级,在视图中选择所示的面,使用缩放工具稍微缩小一点,如图所示。 04 点击【插入设置】,如图所示。 设置插入数量为2.5mm。 05 点击【挤出设置】,如图所示。 ...

youximoxingEDO的博客 3478

《MySQL 亿级数据表平滑分表实践:基于时间分片的架构演进》

MySQL亿级数据表分表实践:基于时间分片的架构演进 本文针对单表数据量过亿的性能瓶颈,提出按时间维度水平分表的解决方案。通过分析4亿级数据表的实际案例,详细介绍了分表前的数据评估、命名规范设计,以及两种平滑迁移方案:1)批处理数据迁移+联合视图过渡;2)触发器实时同步。同时提供Java应用层的动态表名路由与MyBatis适配方案,包括动态SQL拼接和插件拦截两种实现方式。文章重点阐述了如何在不影响线上业务的前提下完成分表改造,解决索引膨胀、DDL锁表等问题,适用于具备明显时间特征的业务数据场景。(149字

私聊前往站内信:https://i.youkuaiyun.com/#/msg/chat/weixin_44976692 2105

遗传算法(Genetic Algorithm)详解与实现

遗传算法(Genetic Algorithm, GA)是受自然进化原理启发的一系列搜索算法。通过模仿自然选择和繁殖的过程,遗传算法可以为涉及搜索、优化和学习的各种问题提供高质量的解决方案。同时,它们类似于自然进化,因此遗传算法可以克服传统搜索和优化算法遇到的一些障碍,尤其是对于具有大量参数和复杂数学表示形式的问题。

盼小辉丶的博客 24万+

【Mongodb-03】亿级数据从mysql迁移到mongodb辛酸历程

亿级数据从mysql迁移到mongodb

zhenghuishengq的博客 7935

Unity 面试篇|(六)数据结构和算法篇 【全面总结 | 持续更新】

先拿10000个数建堆,然后一次添加剩余元素,如果大于堆顶的数(10000中最小的),将这个数替换堆顶,并调整结构使之仍然是一个最小堆,这样遍历完后,堆中的10000个数就是所需的最大的10000个。桶排序是计数排序的升级版。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。这样处理就可以分别在每个文件的10^6个数据中找出最大的10000个数,合并到一起在再找出1000*10000中的最终的结果。

游戏开发小Y的博客 12万+

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

数据迁移项目的踩坑复盘:亿级数据从 MySQL 到 TiDB 的平滑迁移

我们核心业务的订单表在过去三年中从 300 万行增长到 1.8 亿行,MySQL 的分库分表方案逐渐力不从心。跨分片查询需要应用层聚合,运维复杂度随着分片数量线性增长。同时,业务需要实时数据分析能力,而 MySQL 的 OLAP 查询在亿级数据量下经常超时。经过三个月的技术选型和 POC 验证,我们决定将订单相关数据迁移到 TiDB。选择 TiDB 的核心考量有三点:兼容 MySQL 协议和生态,迁移改造成本最低;原生分布式架构支持弹性扩缩容,未来 3~5 年的增长无需担心;

alex_goden的博客 2597

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

MySQL亿级数据平滑迁移实战

举例来说,假设老库的某张表里有数据 b ,其 id=1,此时往该表使用 insert ignore 批量插入三条数据 a,b,c,其在表内的 id 为 a:2、b:1、c:3,返回的影响行数为2,SELECT LAST_INSERT_ID() 返回的是2,因此 Mybatis 往对象里设置的主键分别为 a:2、b:3、c:null,再使用这个设置好 id 的对象列表插入新库时会导致新老库 id 不一致。实际操作过程中,开关的切换的耗时较短,只花了30秒左右,在这过程中没有打印删除数据的日志。

m0_46995061的博客 1109

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

MySQL亿级数据平滑迁移双写方案实战例子解析

需要注意的是,迁移过程中要密切监控数据一致性,确保迁移后新老库数据的完整性和一致性。同时,迁移方案需要根据具体的业务场景和需求进行调整和优化。:vivo游戏中心的预约业务由于历史原因,与其他业务数据表存储在同一个数据库中,这影响了系统的稳定性和可靠性。:考虑到预约业务的读写频率高、不可接受停机、大部分场景能接受秒级数据不一致的特点,选择了双写方案。在处理MySQL亿级数据平滑迁移时,双写方案是一种常见且有效的策略。:迁移过程中,每一步都有回退措施,确保业务不受影响。监控各种异常情况,及时发现并解决问题。

IT老农民的博客 999

KES 数据库迁移实战:从 Oracle/MySQL 到 KingbaseES 的平滑过渡指南

KES 数据库迁移实战:从 Oracle/MySQL 到 KingbaseES 的平滑过渡指南

wei_shuo的博客 1万+

MySQL,100 亿数据,如何不停服平滑迁移?

底层表结构变更;分库个数变化;底层存储介质变化。的众多需求下,需要进行数据迁移,完成“平滑迁移数据,迁移过程不停机,保证系统持续服务”有两种常见的解决方案。服务进行升级,记录“对旧库上的数据修改”的日志;研发一个数据迁移小工具,进行数据迁移;研发一个读取日志小工具,追平数据差异;研发一个数据比对小工具,校验数据一致性;流量切到新库,完成平滑迁移。服务进行升级,记录“对旧库上的数据修改”进行新库的双写;研发一个数据迁移小工具,进行数据迁移;

jike007gt的博客 963

3.亿级积分数据分库分表:ShardingSphere官方提供的平滑数据迁移方案介绍,有什么缺点呢?

ShardingSphere官方提供了平滑数据迁移的方案,这节就简单介绍一下官方方案的操作步骤及原理,以及官方的方案有哪些优缺点,适不适合自己的业务场景

没头脑遇到不高兴 1604
上一篇: MyBatis-Plus 还手写 Join 联表查询?一个依赖轻松搞定,真香!
下一篇: 自己工资 14K,找到月薪 28K工作后,开心地提交辞呈,租了新房子!入职前一天HR说:原来岗位的人不走了,你offer被取消了...
公众号-芋道源码
博客等级 码龄10年 1631粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值