分库分表多数据源切换实战:从路由机制到跨库事务踩坑

发布时间:2026/10/31 11:14:06

分库分表多数据源切换实战:从路由机制到跨库事务踩坑 简介围绕分库分表与多数据源切换的实战示例包面向已有一定Java Web基础、希望理解Sharding-JDBC透明分片机制的开发者与架构学习者可用于搭建分库分表实验环境对照配置与代码验证数据路由和事务管理思路。包内共73个文件以18个Java源码、18个XML配置和21个class编译产物为主体另有properties、jsp、sql脚本及Eclipse工程配置完整保留SSMDemo的Maven项目结构与分片配置线索压缩包约66KB体积轻便便于直接导入调试。已有2070人学习下载说明其在分片入门场景中具有参考价值。读者可从中获取数据源定义、分片规则配置、业务层调用与SQL路由的对应关系并借助示例理解跨库跨表查询、全局唯一标识等常见问题的处理方向适合作为分库分表与多数据源切换的对照样例和排错参考。1. 分库分表之后真正让人失眠的是多数据源切换订单表按用户 ID 拆成 4 库 32 表第一版只做了分片键路由压测跑得挺顺。真正难受的是上线第三个月运营要查跨月对账明细风控要扫全量名单归档任务还要按季度把冷数据搬到历史库一个请求里得在三个库之间来回跳。这时候分库分表本身反而不是最痛的痛的是多数据源切换——谁来决定这次 SQL 走哪个库、切换在事务里为什么不生效、ThreadLocal 忘记清理导致半夜串库这些才是血泪经验集中的地方。下面按「机制 → 手写实现 → 读写分离与跨库事务 → 踩坑清单 → 验证手段」的顺序把这个方案拆开讲适合正在做或准备做分库分表、并且被动态数据源折腾过的后端同学照着复现。2. 数据源切换的底层机制一次 SQL 到底怎么选到那个库2.1 从 getConnection 往下看切换点其实只有一个不管上层包装成什么样Java 侧拿连接的唯一出口是DataSource.getConnection()。Spring 的AbstractRoutingDataSource把这件事做了一层转发它自己不是真正的连接池而是持有一个MapObject, DataSource targetDataSources在getConnection()时调用determineCurrentLookupKey()拿到一个 key用这个 key 去 map 里查真正的数据源查不到就退回defaultTargetDataSource。这条链路展开是这样的事务管理器或 MyBatis 调用DataSourceUtils.getConnection(dataSource)请求打到AbstractRoutingDataSource#getConnection内部触发determineTargetDataSource()→determineCurrentLookupKey()用返回的 key 在resolvedDataSources里查表命中即用拿到的Connection被包成ConnectionHolder注册到TransactionSynchronizationManager。所以后面所有「注解切库」「分片键路由」「读写分离强制走主」本质上都只是往第 3 步那个返回值里塞东西。理解这一点之后很多玄学问题会一下变得有解释为什么在事务方法内部再切一次不生效为什么切了库但 SQL 还打在老库上答案都在第 5 步。切换手段注入点生效时机能否覆盖已开启的事务方法级注解 AOPdetermineCurrentLookupKey的返回值方法进入前不能必须早于事务切面ORM 拦截器改 SQL 库名SQL 文本SQL 执行前能改表名改不了连接手动DataSourceContextHolder.push同上随代码位置不能取决于调用顺序分片中间件路由层SQL 解析后重写解析阶段能但复杂度高一个量级这张表值得存下来。很多团队一开始图省事在 Service 方法里手动push一个库名结果被外层Transactional包住切了等于没切排查半天以为是注解没生效。2.2 三种主流落地方式的选型对比注解切面方案自定义一个DS(order-1)注解AOP 环绕时往 ThreadLocal 写 key。优点是零依赖、可控、出问题能打断点缺点是不认识分片键只能做「方法级」的静态切换一个方法只能锁定一个库。ORM 拦截器方案在 MyBatis 的Interceptor里拦截Executor#query/update从参数里解析分片键动态算出库名表名再改写 SQL。优点是可以做到「一条 SQL 一个路由」跨库 UNION、批量插入都能覆盖。缺点是 SQL 解析容易翻车in条件跨分片、子查询、order by归并都要自己处理。连接池包装方案把多个物理连接池塞进一个逻辑DataSource对外仍暴露一个实例内部根据 key 分发。这个思路就是AbstractRoutingDataSource的加强版好处是对上层完全透明ORM 不需要感知坏处是连接数会翻倍膨胀4 库 × 20 连接 80 个物理连接再叠一套从库就是 160数据库侧max_connections很容易被打满。分片中间件方案把路由、改写、归并全交给中间件层。省事但代价是把一部分控制权交出去了跨库事务、强制走主、影子表这些定制需求往往要靠 Hint 或者改源码。团队规模不大、分片规则又比较固定的时候我一般会选「注解切面 简单分片键计算」够用出问题也查得动。2.3 事务一开后面的切换就白写了这是踩坑最多的一条。DataSourceTransactionManager#doBegin在事务启动时就已经调用了dataSource.getConnection()拿到的连接被绑定到当前线程的ConnectionHolder上。之后业务代码里哪怕把 ThreadLocal 里的 key 改成了order-2MyBatis 执行时走的是SpringManagedTransaction#getConnection它会先去TransactionSynchronizationManager找有没有已绑定的连接——有就直接复用根本不会再去问AbstractRoutingDataSource要新连接。说得更直白一点事务边界决定了数据源边界事务里面切库只能算改了个变量。还有一种更隐蔽的情况两个数据源各自绑定了自己的ConnectionHolder此时同一个线程里会出现两条独立连接、两个独立事务。第一条提交了第二条回滚前面那条不会跟着回滚。这不是「分布式事务没配对」而是本地事务本来就是各管各的。真要跨库一致得往上走本地消息表或者两阶段提交这部分在第 4 章展开。注意把切数据源的切面Order设成比事务切面更小的值比如Ordered.HIGHEST_PRECEDENCE只能保证「切库发生在开事务之前」不能保证「事务中途切库生效」。后者在单事务管理器下无解。3. 手写一套能用的动态路由配置、上下文、切面三段代码3.1 数据源注册与动态路由实例装配先解决「有多少个库、连接参数从哪来」。多库场景下不建议写死多个Bean4 库 32 表还能忍扩到 16 库就是体力活了。用Map结构绑定配置加库只改 YAML。shard: datasource: order-0: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://db-order-0:3306/order_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: app_rw password: ${ORDER0_PWD} maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 3000 validation-timeout: 1000 order-1: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://db-order-1:3306/order_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: app_rw password: ${ORDER1_PWD} maximum-pool-size: 20 minimum-idle: 5Data ConfigurationProperties(prefix shard) public class ShardDataSourceProperties { // key 即逻辑库名与路由 key 保持一致避免两套命名互相翻译 private MapString, HikariDataSource datasource new LinkedHashMap(); } Configuration EnableConfigurationProperties(ShardDataSourceProperties.class) public class DynamicDataSourceConfig { Bean(dynamicDataSource) public DataSource dynamicDataSource(ShardDataSourceProperties props) { MapObject, Object targets new LinkedHashMap(props.getDatasource()); if (targets.isEmpty()) { throw new IllegalStateException(shard.datasource 未配置任何数据源); } DynamicRoutingDataSource ds new DynamicRoutingDataSource(); ds.setTargetDataSources(targets); // 默认库一定要设否则 key 为 null 时直接抛 IllegalStateException ds.setDefaultTargetDataSource(targets.get(order-0)); ds.afterPropertiesSet(); return ds; } } public class DynamicRoutingDataSource extends AbstractRoutingDataSource { Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.peek(); } }这里的几个点值得单独说明。afterPropertiesSet()必须手动调用因为AbstractRoutingDataSource是在这个方法里把targetDataSources转成resolvedDataSources的不调用的话运行期查表永远为空。setDefaultTargetDataSource也别省定时任务、健康检查、启动时的元数据查询都可能在没有任何上下文的情况下发 SQL没有默认库会直接启动失败。HikariDataSource用jdbc-url而不是url这是 Spring Boot 绑定HikariConfig时的约定写成url会绑定不上而且不报错最后表现为「连接串是 null」这类问题查起来很费时间。maximum-pool-size的估算方式是单库承受的连接数 ÷ 应用实例数再留 20% 余量给运维和监控连接。3.2 ThreadLocal 上下文用栈而不是单值很多人第一版会写成ThreadLocalString然后遇到嵌套调用就翻车外层方法指定order-1内层调用一个指定order-0的方法内层执行完把 ThreadLocal 清空了外层后续 SQL 直接掉到默认库上。public final class DataSourceContextHolder { private static final ThreadLocalDequeString HOLDER ThreadLocal.withInitial(ArrayDeque::new); private DataSourceContextHolder() {} public static void push(String key) { if (key ! null !key.isEmpty()) { HOLDER.get().push(key); } } public static void poll() { DequeString deque HOLDER.get(); deque.poll(); if (deque.isEmpty()) { HOLDER.remove(); // 关键不留空壳防止线程池复用时的脏读 } } public static String peek() { DequeString deque HOLDER.get(); return deque.isEmpty() ? null : deque.peek(); } public static void clear() { HOLDER.remove(); } }用Deque做栈式存取push进、poll出天然支持嵌套。poll之后判断为空就remove()这一步是防内存泄漏和脏数据的后悔药——Tomcat 的线程是复用的如果不 remove下一个请求进来peek()可能拿到上一个请求残留的库名表现为「偶发串库」而且极难复现。clear()留给最外层的 Filter 或拦截器兜底在finally里调用一次作为双保险。3.3 注解 AOP方法级切换与分片键占位注解定义要能表达两种意图写死库名或者按参数算库名。Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface DS { String value() default ; // 直接指定如 order-1 String shardKey() default ; // SpEL 表达式如 #userId boolean master() default false; // 强制走主 }Aspect Component Order(Ordered.HIGHEST_PRECEDENCE) // 必须早于 Transactional 的默认优先级 public class DsSwitchAspect implements Ordered { private final ExpressionParser parser new SpelExpressionParser(); private final ParameterNameDiscoverer discoverer new DefaultParameterNameDiscoverer(); Around(annotation(ds)) public Object around(ProceedingJoinPoint pjp, DS ds) throws Throwable { String key resolveKey(pjp, ds); DataSourceContextHolder.push(key); try { return pjp.proceed(); } finally { DataSourceContextHolder.poll(); } } private String resolveKey(ProceedingJoinPoint pjp, DS ds) { if (!ds.shardKey().isEmpty()) { MethodSignature sig (MethodSignature) pjp.getSignature(); EvaluationContext ctx new MethodBasedEvaluationContext( null, sig.getMethod(), pjp.getArgs(), discoverer); Object v parser.parseExpression(ds.shardKey()).getValue(ctx); if (v null) { throw new IllegalArgumentException(DS 分片键解析为空 ds.shardKey()); } return ShardRouter.dbName(String.valueOf(v)); } return ds.master() ? ds.value() -master : ds.value(); } Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } }Order(Ordered.HIGHEST_PRECEDENCE)这一行是整个切面能不能生效的分水岭。Spring 里Transactional的默认 order 是LOWEST_PRECEDENCE数值越小越先执行所以切库切面必须在事务切面之外层。如果写反了事务先开、连接先拿后面 push 什么都没用表现出来的现象就是「注解明明加了SQL 还是打在主库」。分片键用 SpEL 而不是硬编码参数下标好处是方法签名改了不用跟着改切面坏处是表达式写错只有运行时才报建议在单元测试里对每个带DS(shardKey...)的方法都跑一遍。3.4 分片键到库表下标的计算库名和表名的计算必须收敛到一个类里绝对不能散落在各处。散开之后最容易出现的问题是新加一个库老代码里有一处取模常量没改数据写进了不存在的分片。public final class ShardRouter { private static final int DB_COUNT 4; private static final int TABLE_COUNT 8; private static final String PREFIX order-; private ShardRouter() {} public static String dbName(long shardKey) { return PREFIX Math.floorMod(hash(shardKey), DB_COUNT); } public static String tableName(long shardKey) { int db Math.floorMod(hash(shardKey), DB_COUNT); int tb Math.floorMod(hash(shardKey) / DB_COUNT, TABLE_COUNT); return t_order_ (db * TABLE_COUNT tb); } // 位扰动避免连续 ID 在取模后集中落在同一个库 private static int hash(long key) { long h key ^ (key 33); h * 0xff51afd7ed558ccdL; h ^ h 33; return (int) (h ^ (h 32)); } }两个细节。第一先算库再算表的分母要除以DB_COUNT直接用hash % TABLE_COUNT会让库和表的分布产生相关性某些库会明显更热。第二用floorMod而不是%因为分片键如果出现负数比如由雪花 ID 异或出来的中间值%会返回负下标直接拼出order--1这种库名连接直接失败。参数约定上DB_COUNT和TABLE_COUNT一旦定下就不要动扩容时要么做双写迁移要么上一致性哈希。绝大多数业务在 4 库 32 表这个量级能撑很久前期把规则定死反而更省心。4. 分库分表场景下的读写分离与跨库事务4.1 强制走主三种写法和各自的失效条件读写分离接进来之后最典型的翻车是「刚写完立刻查查不到」。原因是从库复制有延迟而查询被路由到了从库。第一种写法是注解上加mastertrue走本章 3.3 的resolveKey把 key 拼成order-0-master。这种写法最直观但要求每个需要强一致的读方法都记得加注解漏一个就是一个线上问题。第二种写法是 Hint在代码里显式DataSourceContextHolder.push(order-0-master)用完poll()。粒度更细能精确到某一行查询代价是可读性差业务代码里混入路由逻辑。第三种是「同事务内自动走主」在AbstractRoutingDataSource的determineCurrentLookupKey里判断TransactionSynchronizationManager.isActualTransactionActive()事务活跃就强制返回主库 key。这一招最省事能覆盖掉大部分写后读场景但要注意它会让「只读事务」也走主库如果只读事务开得多主库压力会上去。还有一类失效场景要单独提异步线程和 MQ 消费线程里ThreadLocal 是取不到值的peek()返回 nullSQL 全部落到默认库。这不是读写分离的问题是上下文传递的问题第 5 章会专门讲。4.2 跨库事务不引入分布式事务框架时怎么兜分库分表之后一个业务操作横跨两个库几乎是必然的。订单落在order-0库存扣减落在inventory-1本地事务管不了对方。能做的主要有三条路。能拆就拆。把跨库操作改成「同库内完成 异步补偿」。比如订单和订单明细用同一个分片键一定落同一个库库存扣减改成发消息由消费方幂等处理。这条路工程量最小但要求业务能接受最终一致很多交易场景不接受。本地消息表。在业务库里建一张t_local_message业务写库和写消息表在同一个本地事务里完成然后由定时任务扫描未发送消息投递出去消费方幂等消费。它保证了「业务成功则消息一定发出」反过来不保证。实现上要注意消息表要跟业务表同库否则又变成跨库了。两阶段提交。适合强一致要求高、且参与方都能支持 XA 的场景。代价是性能下降明显且协调者本身是单点。中小团队一般不会首选。参数和边界上我一般的做法是核心资金链路用本地消息表 对账兜底非核心链路能异步就异步XA 只在极少数必须强一致的场景局部使用。跨库事务这块没有银弹选一条路走通比反复横跳强。4.3 分片键缺失全路由、结果归并与深分页只要查询条件里没有分片键路由层就只有两个选择报错或者全路由。全路由意味着 4 个库并发查、结果在内存里归并order by和limit都得自己实现。-- 没有分片键的查询实际会拆成 4 条下发 SELECT id, user_id, amount, created_at FROM t_order_0 WHERE status 1 AND created_at 2024-01-01 ORDER BY created_at DESC LIMIT 20;归并的坑集中在分页。limit 100000, 20这种深分页如果每个分片都取 100020 条再归并内存直接爆。常见做法是改成游标分页记住上一页最后一条的created_at id下一页用where (created_at, id) (?, ?)往下扫每个分片只需要返回 20 条。还有一类是全路由下的聚合查询count(*)、sum(amount)需要在内存里二次聚合。数据量小的时候没问题量大了会明显拖慢接口。我的做法是给这类报表查询单独建一份宽表或走离线数仓不在在线库上做全路由聚合。5. 多数据源切换的踩坑与排查清单5.1 现象事务方法里切了库SQL 还是打到原库原因事务开启时已经通过DataSourceTransactionManager#doBegin拿到了Connection并绑定到TransactionSynchronizationManager后续getConnection直接复用不再询问路由 key。解决把切库动作提到事务外层也就是让切面 order 小于事务切面实在需要在事务中间换库只能把那段逻辑拆成独立的事务方法用REQUIRES_NEW传播级别重新开一个事务代价是两条连接、两个事务回滚不互通。5.2 现象偶发串库A 用户的数据出现在 B 用户的请求里原因ThreadLocal用完没清理线程池复用线程时带出了上一次的残留值。还有一种变体是poll()之后Deque空了但没remove()ThreadLocal的空壳一直挂在 Thread 上。解决finally里必须调poll()poll()内部判空后remove()最外层加一个Filterfinally里clear()兜底。上线前专门写一个并发测试100 线程跑 1 万次看有没有跨请求的 key 泄漏。5.3 现象应用启动报连接数超限或者运行一段时间数据库侧连接被打满原因每个物理库一个HikariDataSource每个池默认 10 个连接4 库 4 从库 80 连接/实例5 个实例就是 400。再加上定时任务、归档任务各自持有独立数据源数量还会翻。解决统一在配置里给每个池设maximum-pool-size按「单库上限 ÷ 实例数 × 0.8」估算minimum-idle设小一点比如 2避免空闲期占着连接把归档、对账这类离线任务的数据源单独隔离不要和在线流量抢连接。5.4 现象异步线程或 MQ 消费里SQL 全部落到默认库原因ThreadLocal不跨线程传递Async、CompletableFuture、线程池提交的任务里peek()返回 null。解决提交任务前把 key 取出来用包装器带进子线程在子线程的finally里清理或者用TaskDecorator统一给线程池加装饰在run()前后做push/poll。如果用的是 MQ把库名或分片键放进消息体消费端第一件事就是根据消息头push。5.5 现象只读事务里读到旧数据主从延迟只在高峰期出现原因读请求被路由到从库主从复制有毫秒级延迟平时看不出来高峰期写入量大时延迟拉长到几百毫秒。解决写后立即读的场景强制走主用 4.1 的三种方式之一对延迟敏感的业务在从库上暴露Seconds_Behind_Master指标超过阈值自动降级为主库读对账类任务直接走主库不参与读写分离。6. 上线前怎么确认切换真的生效三个可复现的检查手段光靠代码 review 判断切库有没有生效基本等于自欺欺人。我一般会做三件事成本都不高。第一把路由决策打进日志。在determineCurrentLookupKey里打一行 debug带上 key 和当前线程名。压测时打开用grep统计各库的命中次数分布均匀说明路由没问题。这一步曾经帮我抓到过一个 bug分片键的 hash 扰动写错了4 个库的流量分布是 7:1:1:1业务量小的时候完全看不出来。第二用影子单据走一遍真实链路。造一条带特定分片键的测试数据从接口入口调进去然后去目标库直接select验证落库位置同时确认源库没有这条数据。跨库场景下再补一条故意让第二个库的操作失败看第一个库是否按预期回滚或补偿。第三盯连接池指标。每个逻辑库暴露active / idle / wait三个值重点看wait。如果某个库的等待线程数持续上涨说明路由倾斜或者池子太小这两种情况在压测报告里都会被平均 QPS 掩盖掉。# 快速核对某个分片键最终落在哪个库表避免人工推演 curl -s http://localhost:8080/debug/route?userId881234567890 | jq . # {db:order-2,table:t_order_19,key:order-2,thread:http-nio-8080-exec-7}这个 debug 接口只在内网和测试环境开暴露路由结果是排查串库问题最快的手段——线上出问题时先确认「算出来的库」和「实际打到的库」是不是同一个能省掉一大半猜测。最后说一条教训。早些年做第一个分库分表项目我默认「注解加了就万事大吉」结果一个嵌套事务的方法切库失效写了三个月的脏数据才被对账发现。后来养成习惯凡是涉及路由的改动日志、影子单据、连接池指标三样必须过一遍缺一样不上线。这套流程看着笨但比事后修数据便宜太多。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/31 11:09:05

内容分析的类目怎么建才站得住?来源举证、试编码与版本留痕

做内容分析,卡住人的常常不是编码软件,而是那张类目表:类目从哪来、两类能不能分开、改过几版谁说得清。这些问题在开题与审稿现场被追问的频率,远高于样本量。下面给出一套可直接照做的动作:来源举证到句子&#xff0…

2026/10/31 11:09:05

K8s -- 启动一主二从节点

前提: 准备三台机器设置固定ip 主节点:192.168.129.135 从节点1:192.168.129.136 从节点3:192.168.129.137 参考 Ubuntu -- 虚拟机配置固定ip-CSDN博客 基本环境搭建 参考K8S -- 集群搭建环境基础-CSDN博客 配置节点 三台机都设置…

2026/10/31 12:09:10

物理AI技术:多模态感知与工业智能化的未来

1. 物理AI技术爆发的产业背景 2026年将成为物理AI技术发展的关键转折点,这背后有着深刻的产业逻辑和技术积累。物理AI(Physical AI)是指将人工智能技术与物理世界中的实体系统深度融合的技术范式,它正在重塑从制造业到服务业的各个…

2026/10/31 12:09:10

Frida移动安全分析:从基础连接到高级Hook实战指南

1. Frida常用命令全景指南作为移动安全分析领域的瑞士军刀,Frida的命令行工具链提供了从基础连接到高级Hook的全套解决方案。让我们从实战角度剖析这些命令的深层应用逻辑。1.1 环境准备与设备连接设备连接是Frida工作的基础,不同场景需要采用不同的连接…

2026/10/31 12:09:10

DSH-Resume:实现CC、codex、pi会话无缝衔接的归一化中间格式方案

1. 会话接力的真实痛点与 DSH-Resume 的定位 做过 AI 编程助手深度使用的人,大概率都经历过这种场景:在 CC 里跟模型来回聊了半小时,上下文里塞满了项目结构、报错日志、已经试过的方案,结果想换到 codex 上跑一段更长的推理&…

2026/10/31 12:09:10

制造业数智化质量管控系统架构与实施指南

1. 生产线数智化质量管控的核心价值 现代制造业正面临前所未有的转型压力,消费者对产品质量的要求日益严苛,传统人工抽检方式已难以满足需求。我们车间去年因漏检导致的产品召回事件直接损失超过300万元,这个教训促使我们全面转向智能化质量管…

2026/10/31 12:09:10

OpenAI dots:云端AI操作系统与ISAC架构解析

1. 这不是“又一个AI玩具”:OpenAI dots的真实定位与能力边界你刷到这个标题时,第一反应可能是——“又是营销话术”。我完全理解。过去两年,从Copilot到Cursor,从Claude Code到CodeWhisperer,太多工具打着“AI编程助手…

2026/10/31 12:04:09

2026年AI科研前沿:神经符号系统与多模态因果推理

1. 2026年AI科研方案全景解析 2026年的AI科研领域正在经历一场范式转移,从单一模型优化转向系统性创新架构设计。根据最新研究趋势,三大技术支柱构成了当前最前沿的科研方案:神经符号系统融合、多模态因果推理和生物启发计算架构。这些方案正…

2026/10/30 20:55:10

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/30 20:55:10

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/30 20:55:09

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/31 0:02:41

猫狗目标检测数据集:VOC/COCO/YOLO三格式一体化实战包

简介:本资源是一套面向计算机视觉初学者与YOLO目标检测实践者的猫狗图像数据集及配套开发工具包,专为快速上手目标检测模型训练而设计。资源包含1000张真实场景高清猫狗图片,标注质量高,同时提供VOC(XML)、…

2026/10/31 0:02:41

HDU2017多校标程复现:算法竞赛工程化真题沙盒

简介:本资源为2017年HDU多校联合训练第一场官方配套资料,面向ACM程序设计竞赛备赛学生、算法爱好者及高校编程集训队成员,旨在提供权威解题参考与完整测试验证环境。压缩包共40个文件,含15份C标程(覆盖全部赛题&#x…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑