从单库到分库分表:MyBatis+ShardingSphere-JDBC+MySQL实战全解析
最近在把一套老系统从单库单表往分库分表迁移选型的时候没有太多纠结直接把 MyBatis ShardingSphere-JDBC MySQL 这套组合定了下来。原因很简单MySQL 单库在千万级订单量面前明显开始吃力而 ShardingSphere-JDBC 是一个 Java 生态里跟 JDBC 规范天然兼容的分片中间件MyBatis 不需要感知分片逻辑只要把 DataSource 替换掉剩下的路由、拆表、结果归并都交给它处理。这篇文章就把这套组合从配置到踩坑的东西完整过一遍适合正在用 MyBatis 且需要考虑分库分表、读写分离的场景也适合那些项目刚起步、想提前把数据扩展方案留好余地的团队。我尽量讲实际操作少讲虚的。你会看到依赖怎么引、YAML 怎么配、为什么分片键要这样选、二级缓存为什么在这里容易出问题、以及我上线前是怎么验证路由结果的。1. 一次SQL请求到底经过了多少层MyBatis、JDBC和ShardingSphere的角色定位1.1 三层各自的边界要理解这套组合必须先搞清楚一个请求从 Mapper 接口到 MySQL 实例中间发生了什么事。很多同学把 MyBatis 和 JDBC 混着说加上 ShardingSphere 之后更乱。其实边界很清晰MyBatis负责 ORM 映射。它把OrderMapper.selectById(1)这个方法调用转换成一条 SQL 字符串然后通过SqlSession拿到 Connection 执行。JDBC是 Java 访问关系型数据库的标准接口。DriverManager或者DataSource负责获取 ConnectionStatement负责执行 SQLResultSet负责装回结果。ShardingSphere-JDBC正好夹在中间。它包装了你的真实数据源对外暴露一个DataSource接口。当 MyBatis 从它身上拿 Connection 时它不会直接给你 MySQL 的物理连接而是先做 SQL 解析、路由、改写、归并最后才通过底层真实的 JDBC 连接把改写后的 SQL 发给目标数据库实例。拿一个查询举例MyBatis 想执行SELECT * FROM t_order WHERE order_id 1001ShardingSphere 看到这条逻辑 SQL 后按照配置的分片规则算出 order_id1001 应该落在ds0.t_order_1于是它把 SQL 改写成SELECT * FROM t_order_1 WHERE order_id 1001然后从 ds0 对应的 HikariCP 连接池里拿一条真实 JDBC Connection 发出去。1.2 ShardingSphere-JDBC 和普通连接池的差别很多人第一次用的时候会想“我是不是要把 HikariCP 换成 ShardingSphere它是不是一个连接池” 不是。它更像一个“数据源拦截器”。HikariCP 负责管理真实连接ShardingSphere 负责在这些连接之上做路由和改写。你可以把连接池想象成快递公司的仓库ShardingSphere-JDBC 就是分拣中心快递员MyBatis把包裹随便丢进来分拣中心根据地址分片键把包裹重新分配到不同仓库物理库表最后再由仓库里的货车JDBC 驱动送到目的地。如果你只用普通连接池一个DataSource对应一个 MySQL 库。但 ShardingSphere 的数据源配置里可以挂多个真实数据源比如ds0、ds1每个数据源可以指向一个 MySQL 实例。它对外仍然只有一个DataSourceMyBatis 完全无感。这里要特别提醒一句ShardingSphere-JDBC 解析、改写 SQL 本身有开销但它是在应用进程里完成的没有额外的网络 RTT性能损耗相对可控。如果你的表只有几十万行就别上分片纯属给自己找麻烦。2. 环境准备中的版本陷阱与依赖配置2.1 我选定的版本组合版本组合这事直接决定你能不能跑通。我这次用的是 MySQL 8.0.33、MySQL 官方 JDBC 驱动 8.0.33、MyBatis 3.5.15、ShardingSphere-JDBC 5.4.1、HikariCP 5.0.1。这套组合相对成熟ShardingSphere 5.x 的 YAML 配置和 4.x 差别很大别拿旧配置去套新版本否则会报一堆莫名奇妙的属性错误。组件版本说明MySQL Server8.0.33使用 caching_sha2_password 认证插件mysql-connector-j8.0.33官方 JDBC 驱动MyBatis3.5.15较稳定的版本支持 JDK 8ShardingSphere-JDBC5.4.1核心包名改为shardingsphere-jdbc-coreHikariCP5.0.1连接池ShardingSphere 本身不实现连接池Maven 依赖这样写dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core/artifactId version5.4.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.15/version /dependency dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.0.1/version /dependency注意 ShardingSphere 5.x 的 GAV 是org.apache.shardingsphere:shardingsphere-jdbc-core不是shardingsphere-jdbc。这两个名字在不同的版本里混用过搜索资料的时候要留个心。2.2 Maven 依赖下载失败的两种常见原因我在项目里遇到过几次 Maven 下载失败甚至 IDEA 提示Download from Maven failed。排查下来基本就两类原因。第一类是仓库地址问题。公司私服里没有同步 Apache ShardingSphere 的某些 artifact或者本地的 Maven 中央仓库镜像不完整。最简单的处理是在settings.xml里配置一个全量镜像源。国内镜像速度也更快我用过阿里云的公共仓库配置是mirror idaliyunmaven/id mirrorOf*/mirrorOf nameAliyun Public Repository/name urlhttps://maven.aliyun.com/repository/public/url /mirror第二类是驱动包和项目 JDK 版本不匹配。比如mysql-connector-j8.0.x 需要 JDK 8 以上如果你的项目还在 JDK 7就只能用 5.1.x 的老驱动。建议先确认mvn dependency:tree里没有意外升级冲突再检查 IDE 的 Runner 设置的 JVM 参数有时候自定义的 VM options 会覆写全局 JDK 版本。2.3 连接 MySQL 时最容易踩的 SSL 与时区坑搜索“mysql ssl连接错误”能翻出一大堆案例我也在这里卡过一次。MySQL 8 默认开启 SSL而 JDBC URL 里的useSSL如果不显式设置驱动会做 SSL 握手在部分网络环境和自签名证书下会直接报Communications link failure或者The driver could not establish secure connection。我现在的做法是开发环境直接关闭 SSL并顺手把公钥检索和时区都写在 URL 里jdbc:mysql://127.0.0.1:3306/db0?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4这里有两个参数必须解释一下allowPublicKeyRetrievaltrue问题MySQL 8 的 caching_sha2_password 插件做认证时如果连接没有先用 SSL 加密客户端需要从服务端获取 RSA 公钥来做密码加密传输。驱动可能因为无法获取公钥而报错。在可信的内网环境设置allowPublicKeyRetrievaltrue可以绕过这个步骤但生产环境如果网络链路不安全最好还是保留 SSL改成useSSLtrue并配置证书路径。serverTimezoneAsia/Shanghai也很重要。如果 JDBC 时区和 MySQL 系统时区不一致PreparedStatement.setTimestamp传参时会出现 8 小时的偏差查出来的数据就像“穿越”了。把这些参数放在 ShardingSphere 数据源配置里因为 ShardingSphere 不会替你做驱动层面的参数豁免。3. MyBatis 侧要提前做好的三件事Configuration、TypeHandler 和二级缓存3.1 XMLConfigBuilder 与自定义 ConfigurationMyBatis 的初始化过程说到底就是XMLConfigBuilder读取mybatis-config.xml然后把各个标签解析成Configuration对象。如果你追过源码会看到它把settings、typeAliases、mappers等节点逐个解析最终生成一个完整的Configuration。很多人在面试题里碰到“mybatis 中 xmlconfigbuilder 的工作流程”答案其实就是创建解析器、解析根节点、逐项构建配置、返回Configuration。实际开发中我们不会去手写 XMLConfigBuilder但一定要知道Configuration里的几个关键设置项。我至少会改这三个settings setting namemapUnderscoreToCamelCase valuetrue/ setting namedefaultExecutorType valueREUSE/ setting namecallSettersOnNulls valuetrue/ /settingsmapUnderscoreToCamelCase解决数据库字段下划线和 Java 属性驼峰映射否则你要写一堆resultMap。defaultExecutorType设为REUSE会复用 PreparedStatement相对 SIMPLE 能减少一次编译在频繁调用 Mapper 的场景下有一点性能帮助。callSettersOnNulls则是为了避免查询结果某个字段为 null 时MyBatis 直接把整个属性跳过导致对象某些字段不完整。如果你用 Spring Boot可以通过实现ConfigurationCustomizer在启动时微调 ConfigurationComponent public class MyConfigurationCustomizer implements ConfigurationCustomizer { Override public void customize(Configuration configuration) { configuration.setMapUnderscoreToCamelCase(true); configuration.setCallSettersOnNulls(true); configuration.setExecutorType(ExecutorType.REUSE); } }3.2 TypeHandler类型转换机制TypeHandler 是 MyBatis 里很容易被忽略但很实用的扩展点。它的作用是在 Java 类型和 JDBC 类型之间做转换。像 MySQL 的 JSON 字段、PostgreSQL 的数组类型默认映射器往往只能拿出字符串或二进制这时候就需要自定义 TypeHandler。如果你的分片键是字符串同时底层又是雪花算法生成的长整型那请一定注意 Mapper XML 里参数的jdbcType有没有写错。搜索“mybatis中typehandler的工作流程图”的同学大多是想搞明白setParameter和getResult这两个核心方法我简单说下写入时MyBatis 调用setParameter把 Java 对象转换成对应的PreparedStatement参数读取时getResult把ResultSet中的列转换成 Java 对象。举个例子订单表有个extra_info字段数据库里是 JSONJava 里是OrderExtInfoMappedTypes(OrderExtInfo.class) MappedJdbcTypes(JdbcType.VARCHAR) public class OrderExtInfoTypeHandler extends BaseTypeHandlerOrderExtInfo { private static final ObjectMapper MAPPER new ObjectMapper(); Override public void setNonNullParameter(PreparedStatement ps, int i, OrderExtInfo parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, MAPPER.writeValueAsString(parameter)); } Override public OrderExtInfo getNullableResult(ResultSet rs, String columnName) throws SQLException { return parse(rs.getString(columnName)); } Override public OrderExtInfo getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return parse(rs.getString(columnIndex)); } Override public OrderExtInfo getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return parse(cs.getString(columnIndex)); } private OrderExtInfo parse(String value) { if (value null) return null; try { return MAPPER.readValue(value, OrderExtInfo.class); } catch (Exception e) { throw new RuntimeException(e); } } }在 mybatis-config.xml 里注册后Mapper 的 ResultMap 就可以直接用了。这个能力在分片场景下特别重要因为 ShardingSphere 会做结果归并如果你没有注册正确的 TypeHandler归并得到的ResultSet在列名映射时很容易拿到 null 或者类型转换错误。3.3 二级缓存为何在此场景慎用MyBatis 二级缓存是跨 SqlSession 的全局缓存默认不带需要手动开启。很多人一搜“mybatis二级缓存实现”就直接往项目里加但在 ShardingSphere 场景下我强烈建议先想清楚缓存维度。问题在于 ShardingSphere 会对逻辑 SQL 做改写逻辑表名会变成物理表名。如果你的二级缓存以逻辑表名为 key那么一次查询的结果会被缓存到t_order这个 key 下面。下次另一个 route 到t_order_0的查询来了MyBatis 发现缓存命中直接把t_order的缓存返回这个数据可能根本不是目标分片上的数据。更麻烦的是二级缓存存储的是一条 SQL 在逻辑表维度的结果如果分片键没参与 SQL 条件ShardingSphere 会把多个分片的查询结果归并之后才返回给 MyBatis。MyBatis 缓存的这个“归并后的整体结果”其实是安全的但前提是缓存 key 必须包含所有分片查询的实际条件而 MyBatis 默认的缓存 key 只包含逻辑 SQL 和参数这一点在复杂查询下很容易踩坑。我现在的处理规则是静态字典表可以开二级缓存表结构不分片缓存风险小。分片业务表不集中开启二级缓存只在代码里对 Mapper 方法单独使用useCachefalse。如果确实要做缓存建议用 Redis 自己做业务缓存而不是依赖 MyBatis 二级缓存。4. 把 ShardingSphere-JDBC 挂进 MyBatis分片规则与 SQL 路由实战4.1 数据源工厂如何包装原连接池ShardingSphere-JDBC 官方提供YamlShardingSphereDataSourceFactory用来读取 YAML 配置并创建最外层的 DataSource。这一步其实很简单难点在于后续接入 MyBatis 的方式。传统的 MyBatis 单独使用是给SqlSessionFactoryBuilder传一个Reader里面加载 mybatis-config.xml同时通过environments里的dataSource定义数据库连接。但一旦使用 ShardingSphere这个 DataSource 就应该由 ShardingSphere 直接管理MyBatis 只需要接收一个现成的DataSource实例。我通常会自己写一个配置类Configuration public class DataSourceConfig { Bean public DataSource shardingDataSource() throws Exception { return YamlShardingSphereDataSourceFactory.createDataSource( new File(classpath:sharding.yaml)); } Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(classpath:mapper/**/*.xml)); factoryBean.setConfiguration(configuration()); return factoryBean.getObject(); } Bean public Configuration configuration() { Configuration cfg new Configuration(); cfg.setMapUnderscoreToCamelCase(true); cfg.setCallSettersOnNulls(true); return cfg; } }如果你用 Spring Boot记得排除其自动配置的数据源或者在application.yml里把spring.datasource.type指向 ShardingSphere 的类。否则 Spring Boot 会先帮你建一个 HikariCP 的 DataSource接着 ShardingSphere 又要建一个导致 MyBatis 拿到的不是分片数据源。4.2 分片规则配置订单表按月分片分片规则是最需要动脑的地方。我拿订单表举例下面这个配置做了两个维度按user_id决定进哪个库按order_id决定进哪张表。实际项目里可能比这复杂但套路是一致的。dataSources: ds0: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3306/order_db_0?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root123 ds1: dataSourceClassName: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3306/order_db_1?useSSLfalseserverTimezoneAsia/Shanghai username: root password: root123 rules: - !SHARDING tables: t_order: actualDataNodes: ds${0..1}.t_order_${0..1} databaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: user_db_inline tableStrategy: standard: shardingColumn: order_id shardingAlgorithmName: order_table_inline keyGenerateStrategy: column: order_id keyGeneratorName: snowflake shardingAlgorithms: user_db_inline: type: INLINE props: algorithm-expression: ds${user_id % 2} order_table_inline: type: INLINE props: algorithm-expression: t_order_${order_id % 2} keyGenerators: snowflake: type: SNOWFLAKE props: sql-show: true这个配置里的actualDataNodes说的是逻辑表t_order实际存在的物理表集合。ShardingSphere 看到ds${0..1}.t_order_${0..1}就会展开成ds0.t_order_0、ds0.t_order_1、ds1.t_order_0、ds1.t_order_1一共四张表。databaseStrategy和tableStrategy分别指定库分片、表分片的路由列和算法。INLINE算法就是写一个 Groovy 风格的表达式注意它只有一个输入参数就是分片列的值。user_id % 2决定库序号order_id % 2决定表序号。这里有一个关键点为什么分库键选user_id分表键选order_id而不是统一用一个键因为业务里绝大多数查询是“查某个用户的所有订单”此时 SQL 条件里带user_idShardingSphere 可以先按 user_id 定位到具体库再在库内部聚合所有表的数据如果查询条件只带order_id它可以根据 order_id 的奇偶性直接定位到具体表。两个维度互相配合能精准覆盖两类高频查询。4.3 事务处理JDBC 本地事务与 ShardingSphere 强一致性事务这条线值得单独说。ShardingSphere-JDBC 默认的事务行为和普通 JDBC 事务一样connection.setAutoCommit(false)、connection.commit()、connection.rollback()都有。但前提是操作的所有数据都在同一个数据库实例上。如果一次业务操作要更新ds0和ds1两张表本地事务就管不住了这属于分布式事务问题。ShardingSphere 5.x 支持三种事务模式本地事务默认不引入额外组件适合单分片内操作。如果你只在同一个库内操作多张业务表完全够用。XA 事务基于两阶段提交协议会引入 Atomikos 之类的组件。跨分片强一致但性能损耗明显而且要对全局锁和事务日志做进一步配置。BASE 事务基于柔性事务通常要搭配 Seata 使用。适合最终一致性要求的业务场景比如下单后异步扣库存。坦白说我这次迁移没碰 XA因为订单创建、支付回调这些核心链路都能通过合理的设计控制在同一个库内完成。例如把某个用户的所有订单都分发到固定的 ds再在 ds 内部按 order_id 分表那么“同一个用户创建订单”就一定落在同一库本地事务就能覆盖。真正的跨库事务只出现在一些统计类操作里这类操作本来就可以走离线离线分析不需要强一致。用 JDBC 事务时还有一个容易犯的错获取 Connection 后一定要在 finally 块里释放ShardingSphere 包装的连接池如果没有正确归还连接会导致连接耗尽。我见过不少案例直接报Connection is not available, request timed out。5. 上线前必做的路由验证与性能对比5.1 如何确认 SQL 真的路由到了目标分片配置完 ShardingSphere 后第一件事不是写业务而是验证路由结果。很多人配完发现查询结果不对或慢就是因为路由没生效SQL 跑到全表扫描了。最少在props里打开sql-show: true然后执行一条带分片键的查询控制台会打印类似这样的日志ShardingSphere-SQL: Logic SQL: SELECT * FROM t_order WHERE order_id 123456 ShardingSphere-SQL: Actual SQL: ds0 ::: SELECT * FROM t_order_0 WHERE order_id 123456看到这两行说明路由正常。如果打印出来的 Actual SQL 仍然是t_order那说明 ShardingSphere 没接管数据源或者配置里actualDataNodes写错了表被当成了单表处理。还有一种情况是查询条件里没带分片键比如SELECT * FROM t_order WHERE status 1。此时 ShardingSphere 只能做全路由你会看到它把逻辑 SQL 广播到 ds0 和 ds1 的t_order_0、t_order_1四张物理表然后做结果归并。全路由不是 bug但高并发下性能会很差线上要尽量避免。给这种表加上索引、或者通过内部分片键补齐条件是更务实的做法。5.2 同一条 SQL 在单库和分片下的表现对比我迁移前后做了一个简单压测这里把数据整理成表格仅供思路参考。测试表是订单表单表 1000 万行分片后每张物理表 250 万行共 4 张表。压测条件是固定order_id做等值查询并发 100。场景SQL平均响应时间耗时说明单库单表SELECT * FROM t_order WHERE order_id ?42ms走二级索引但单表数据量大Buffer Pool 命中率偏低分片后SELECT * FROM t_order WHERE order_id ?19ms路由到单一物理表表体积小缓存命中率明显提升全路由查询SELECT * FROM t_order WHERE status ?168ms广播到 4 张表归并结果导致耗时上升从表里能明显看出来分片不是万能的。等值查询带分片键时收益最大全路由查询反而比单库还慢因为要收集多个分片的数据再归并。所以分片设计必须和业务查询路径深度绑定而不是机械地按 ID 随机拆表。5.3 分页与排序在分片下的特殊处理分页是另一个隐藏深坑。普通单表分页LIMIT 100000, 20数据库只需要扫描到第 100020 行。但 ShardingSphere 要把每个分片上LIMIT 100000, 20的结果都取回来再在应用层归并最后再丢弃前面多余的记录。也就是说偏移量越大内存和 CPU 消耗越高。碰到这种情况我建议改成“游标分页”或“key-based 分页”。例如把分页查询改成SELECT * FROM t_order WHERE order_id #{lastOrderId} ORDER BY order_id ASC LIMIT 20这样每个分片都可以快速拿到自己要的 20 条ShardingSphere 归并时只需要对最多 40 条记录排序性能非常稳定。如果你的业务必须用页码跳转那就只能全路由加上内存归并代价是会随页数显著增长。排序字段也尽量选择和分片键相同的字段。如果排序字段不是分片键ShardingSphere 会做一次精准的全局归并排序但需要把每个分片里符合条件的记录都加载到内存。数据量一大很容易触发 OOM。6. 上一个坑之后关于分片键的思考这个标题可能有点怪但我确实想单独拎出来说说。我见过太多人为了“均匀分布”而选了一个业务上完全没有查询价值的列当分片键比如纯随机生成的batch_id。结果导入数据的时候是均匀了业务查询却全部变成全路由在线系统卡到怀疑人生。正确做法应该是先拉出线上 Top 50 慢 SQL统计哪些查询条件经常出现然后选“出现频率最高、选择性又很强”的列做分片键。比如交易系统用户 ID 和订单 ID 几乎出现在每一个查询里那分库分表就围绕它们来设计。如果一个业务表只能通过一个非分片键的字段查询那你要么接受全路由要么再加一张映射表。另一个细节是分布式主键。ShardingSphere 内置了雪花算法可以在逻辑 SQL 执行时自动为主键生成全局 ID。雪花算法生成的 ID 是趋势递增的对范围查询友好而且能保证全局唯一。但要注意如果分片键本身就是这个 ID那雪花算法里机的 ID 分布是均匀的取模一般没问题。如果你打算用数据库自增主键分库后千万不能用auto_increment否则同一张逻辑表的物理表各自生成同一个 ID联合查询直接错乱。我最后再分享一个操作习惯配置完分片规则先用几条典型 SQL 在测试环境跑一遍把逻辑 SQL 和 Actual SQL 全部打出来核对。包括等值查询、排序分页、批量插入、跨库 join 这几种情况都过一遍比上线之后在日志里发现问题要高效得多。分片这件事设计阶段花的时间越少上线之后花的时间越多。这套组合目前已经稳定跑了六周订单类查询响应时间下降超过一半。如果你的项目也正卡在 MySQL 单库性能上可以从这套方案开始试。

相关新闻

AWVS 14安装与生产级部署实战指南

AWVS 14安装与生产级部署实战指南

1. 项目概述:AWVS 14到底是什么,它解决的是哪类人的哪类问题?Acunetix Web Vulnerability Scanner(简称AWVS)是网络安全领域里一款久负盛名的自动化Web应用安全扫描工具。它不是黑客玩具,也不是CTF比赛里的…

2026/10/1 19:29:11 阅读更多 →
YOLO安全监控系统落地:从数据集构建到告警事件生成

YOLO安全监控系统落地:从数据集构建到告警事件生成

简介:这份基于YOLO的安全监控系统设计压缩包,面向毕业设计、课程设计与期末大作业场景,借助深度学习目标检测技术解决实时视频流中的物体识别与安全预警问题,适合具备一定Python与神经网络基础的学习者。包内共15个文件&#xff0…

2026/10/1 19:29:11 阅读更多 →
Ubuntu手动配置certbot泛域名证书全流程:DNS验证与Nginx部署

Ubuntu手动配置certbot泛域名证书全流程:DNS验证与Nginx部署

1. 为什么我建议你在 Ubuntu 上手动走一遍 certbot 泛域名证书流程直接说结论:虽然大多数教程都推荐“一键签发、自动续期”,但真实生产环境里,真正卡人的往往不是 Let‘s Encrypt 本身,而是 DNS 解析、权限路径、证书链拼接这些“…

2026/10/1 19:29:11 阅读更多 →

最新新闻

【Redis】高阶使用(一):几个高级命令与 TaoToken 统一 Key 通道的调试实践

【Redis】高阶使用(一):几个高级命令与 TaoToken 统一 Key 通道的调试实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 20:15:38 阅读更多 →
嵌入式驱动开发核心揭秘:芯片手册、设备树与内核实战

嵌入式驱动开发核心揭秘:芯片手册、设备树与内核实战

1. 破题:嵌入式驱动开发到底忙些什么如果你去招聘软件搜“嵌入式驱动开发”,大概率会被各种岗位要求迷住眼:熟悉Linux内核、掌握设备树、会看芯片手册、懂cache一致性、了解中断上下文……有朋友私信问我,这个岗位到底天天在忙些什…

2026/10/1 20:15:38 阅读更多 →
嵌入式驱动开发实战:从寄存器与设备树到Linux驱动调试

嵌入式驱动开发实战:从寄存器与设备树到Linux驱动调试

1. 嵌入式驱动开发到底在忙什么我把这行标题当个引子,想聊聊这些年实际做嵌入式驱动开发的日常。真要说起来,驱动开发并不是一个凭空冒出来的岗位,它是硬件和系统软件之间的桥梁。忙啥咧?三个字概括的话——打交道:跟芯片手册打交…

2026/10/1 20:15:38 阅读更多 →
TM1200上云PLC实战:从接线到云端数据采集与远程运维

TM1200上云PLC实战:从接线到云端数据采集与远程运维

这两年做产线设备改造,十家里有七八家上来就问“能不能上云”“数据能不能远程看”。传统PLC在现场跑得稳,但一到数据采集和远程运维就有点力不从心——要么靠上位机开着软件盯着,要么设备坏了必须人到现场,问题响应慢、成本高。手…

2026/10/1 20:15:38 阅读更多 →
嵌入式驱动开发到底在忙什么?字符设备、设备树与调试实战

嵌入式驱动开发到底在忙什么?字符设备、设备树与调试实战

干了这么多年嵌入式,经常有朋友问我:“驱动开发到底忙啥咧?” 这问题看着简单,但真要展开说,能聊一晚上。嵌入式驱动开发不是个“调寄存器”的体力活,它是在操作系统和硬件之间架桥,而这座桥的质…

2026/10/1 20:15:38 阅读更多 →
Claude Code 常用命令速查手册:TaoToken 配置与验证备忘

Claude Code 常用命令速查手册:TaoToken 配置与验证备忘

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 20:14:38 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/1 19:40:48 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/10/1 19:41:40 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/10/1 20:05:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →