当前位置: 首页 > news >正文

每天批次导入 100 万对账数据到 MySQL 时出现死锁

一、死锁原因及优化策略

1.1 死锁原因分析

  1. 批量插入事务过大
    • Spring Batch 默认将整个 chunk(批量数据块)作为一个事务提交,100 万数据可能导致事务过长,增加锁竞争。
  2. 并发写入冲突
    • 多个线程或批处理作业同时写入同一表,争夺行锁或表锁。
  3. 索引缺失或不当
    • 缺少主键或唯一索引,导致插入时全表扫描。
    • 索引过多导致更新锁冲突。
  4. 分库分表未优化
    • 单表数据量过大(如超过千万),查询和插入性能下降。
    • 分片键设计不合理,导致热点数据集中。
  5. 拒绝策略或线程池配置不当
    • 动态线程池(如 Dynamic TP)配置不当,导致任务积压或拒绝,间接增加事务等待时间。
  6. 事务隔离级别
    • MySQL 默认 REPEATABLE_READ 可能引发间隙锁,尤其在范围更新或插入时。

1.2 优化策略

  1. 分批提交
    • 将 100 万数据拆分为小批量(如每 1000 条一个事务),减少事务持有锁时间。
  2. 动态线程池优化
    • 使用动态线程池(如 Dynamic TP)控制并发,限制同时写入的线程数。
    • 配置合理的拒绝策略(如 CallerRunsPolicy)避免任务丢失。
  3. 分库分表
    • 使用 ShardingSphere 按对账 ID 或日期分片,分散数据压力。
    • 优化分片键,避免热点。
  4. 索引优化
    • 确保主键和必要索引存在,避免全表扫描。
    • 移除冗余索引,减少锁冲突。
  5. 事务隔离级别调整
    • 评估是否可降低为 READ_COMMITTED,减少间隙锁。
  6. 死锁检测与重试
    • 配置 MySQL 死锁检测(innodb_deadlock_detect)。
    • 在代码中实现重试机制。
  7. AOP 监控
    • 使用 AOP 记录批量导入性能和死锁异常,便于定位问题。
  8. 日志与监控
    • 集成 ActiveMQ 记录操作日志,Actuator 监控线程池和数据库性能。

二、在 Spring Boot 中实现优化方案

以下是在 Spring Boot 中实现批量导入 100 万对账数据的示例,使用 Spring BatchShardingSphere(分库分表)、Dynamic TP(动态线程池)、AOP 监控等,解决死锁问题。

2.1 环境搭建

2.1.1 配置步骤
  1. 创建 Spring Boot 项目

    • 使用 Spring Initializr 添加依赖:
      • spring-boot-starter-web
      • spring-boot-starter-data-jpa
      • mysql-connector-java
      • shardingsphere-jdbc-core
      • dynamic-tp-spring-boot-starter
      • spring-boot-starter-activemq
      • spring-boot-starter-batch
      • spring-boot-starter-aop
    <project><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.0</version></parent><groupId>com.example</groupId><artifactId>batch-import-demo</artifactId><version>0.0.1-SNAPSHOT</version><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core</artifactId><version>5.4.0</version></dependency><dependency><groupId>cn.dynamictp</groupId><artifactId>dynamic-tp-spring-boot-starter</artifactId><version>1.1.5</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-activemq</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-batch</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies>
    </project>
    
  2. 准备数据库

    • 创建两个 MySQL 数据库:recon_db_0recon_db_1
    • 每个数据库包含两个表:reconciliation_0reconciliation_1
    • 表结构:
      CREATE TABLE reconciliation_0 (id BIGINT PRIMARY KEY,account_id VARCHAR(50),amount DECIMAL(10,2),recon_date DATE,INDEX idx_account_id (account_id),INDEX idx_recon_date (recon_date)
      );
      CREATE TABLE reconciliation_1 (id BIGINT PRIMARY KEY,account_id VARCHAR(50),amount DECIMAL(10,2),recon_date DATE,INDEX idx_account_id (account_id),INDEX idx_recon_date (recon_date)
      );
      
  3. 配置 application.yml

    spring:profiles:active: devapplication:name: batch-import-demoshardingsphere:datasource:names: db0,db1db0:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/recon_db_0?useSSL=false&serverTimezone=UTCusername: rootpassword: rootdb1:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/recon_db_1?useSSL=false&serverTimezone=UTCusername: rootpassword: rootrules:sharding:tables:reconciliation:actual-data-nodes: db${0..1}.reconciliation_${0..1}table-strategy:standard:sharding-column: idsharding-algorithm-name: recon-table-algodatabase-strategy:standard:sharding-column: idsharding-algorithm-name: recon-db-algosharding-algorithms:recon-table-algo:type: INLINEprops:algorithm-expression: reconciliation_${id % 2}recon-db-algo:type: INLINEprops:algorithm-expression: db${id % 2}props:sql-show: truejpa:hibernate:ddl-auto: noneshow-sql: truebatch:job:enabled: falseinitialize-schema: alwaysactivemq:broker-url: tcp://localhost:61616user: adminpassword: admin
    server:port: 8081
    management:endpoints:web:exposure:include: health,metrics,threadpool
    dynamic-tp:enabled: trueexecutors:- thread-pool-name: batchImportPoolcore-pool-size: 4max-pool-size: 8queue-capacity: 1000queue-type: LinkedBlockingQueuerejected-handler-type: CallerRunsPolicykeep-alive-time: 60thread-name-prefix: batch-import-
    logging:level:root: INFOcom.example.demo: DEBUG
    
  4. MySQL 配置

    • 确保死锁检测启用:
      SET GLOBAL innodb_deadlock_detect = ON;
      
    • 调整事务隔离级别(可选):
      SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
      
2.1.2 原理
  • ShardingSphere:按 ID 哈希分片,分散数据到 db0.reconciliation_0, db0.reconciliation_1, db1.reconciliation_0, db1.reconciliation_1
  • Dynamic TP:控制批量导入的并发线程数,优化资源利用。
  • Spring Batch:分 chunk 处理数据,减少事务大小。
  • AOP:监控导入性能和死锁。
2.1.3 优点
  • 分库分表降低单表压力。
  • 动态线程池优化并发。
  • 小批量事务减少锁竞争。
2.1.4 缺点
  • 配置复杂,需熟悉 ShardingSphere 和 Dynamic TP。
  • 跨库事务需额外支持。
  • 死锁监控增加少量开销。
2.1.5 适用场景
  • 高并发批量数据导入。
  • 大数据量对账系统。
  • 微服务数据库优化。

2.2 实现批量导入

实现 100 万对账数据的批量导入,优化死锁问题。

2.2.1 配置步骤
  1. 实体类Reconciliation.java):

    package com.example.demo.entity;import jakarta.persistence.Entity;
    import jakarta.persistence.Id;
    import java.math.BigDecimal;
    import java.time.LocalDate;@Entity
    public class Reconciliation {@Idprivate Long id;private String accountId;private BigDecimal amount;private LocalDate reconDate;// Getters and Setterspublic Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getAccountId() { return accountId; }public void setAccountId(String accountId) { this.accountId = accountId; }public BigDecimal getAmount() { return amount; }public void setAmount(BigDecimal amount) { this.amount = amount; }public LocalDate getReconDate() { return reconDate; }public void setReconDate(LocalDate reconDate) { this.reconDate = reconDate; }
    }
    
  2. RepositoryReconciliationRepository.java):

    package com.example.demo.repository;import com.example.demo.entity.Reconciliation;
    import org.springframework.data.jpa.repository.JpaRepository;public interface ReconciliationRepository extends JpaRepository<Reconciliation, Long> {
    }
    
  3. 服务层ReconciliationService.java):

    package com.example.demo.service;import com.example.demo.entity.Reconciliation;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.batch.core.Job;
    import org.springframework.batch.core.JobParametersBuilder;
    import org.springframework.batch.core.launch.JobLauncher;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.util.JdbcUtils;
    import org.springframework.stereotype.Service;import java.sql.SQLException;@Service
    public class ReconciliationService {private static final Logger logger = LoggerFactory.getLogger(ReconciliationService.class);private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();@Autowiredprivate JobLauncher jobLauncher;@Autowiredprivate Job importReconJob;public void startImportJob() {try {CONTEXT.set("Import-" + Thread.currentThread().getName());logger.info("Starting batch import job");JobParametersBuilder params = new JobParametersBuilder().addLong("timestamp", System.currentTimeMillis());jobLauncher.run(importReconJob, params.build());} catch (Exception e) {logger.error("Failed to start import job", e);} finally {CONTEXT.remove();}}public void retryOnDeadlock(Runnable task, int maxRetries) {int retries = 0;while (retries < maxRetries) {try {task.run();return;} catch (Exception e) {if (isDeadlock(e)) {retries++;logger.warn("Deadlock detected, retrying {}/{}", retries, maxRetries);try {Thread.sleep(100 * retries); // 指数退避} catch (InterruptedException ie) {Thread.currentThread().interrupt();}} else {throw e;}}}throw new RuntimeException("Max retries reached for deadlock");}private boolean isDeadlock(Exception e) {return e.getCause() instanceof SQLException &&((SQLException) e.getCause()).getErrorCode() == 1213;}
    }
    
  4. Spring Batch 配置BatchConfig.java):

    package com.example.demo.config;import com.example.demo.entity.Reconciliation;
    import org.dynamictp.core.DtpRegistry;
    import org.dynamictp.core.executor.DtpExecutor;
    import org.springframework.batch.core.Job;
    import org.springframework.batch.core.Step;
    import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
    import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
    import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
    import org.springframework.batch.item.ItemProcessor;
    import org.springframework.batch.item.ItemReader;
    import org.springframework.batch.item.ItemWriter;
    import org.springframework.batch.item.database.JpaItemWriter;
    import org.springframework.batch.item.support.ListItemReader;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import jakarta.persistence.EntityManagerFactory;
    import java.math.BigDecimal;
    import java.time.LocalDate;
    import java.util.ArrayList;
    import java.util.List;@Configuration
    @EnableBatchProcessing
    public class BatchConfig {@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Autowiredprivate EntityManagerFactory entityManagerFactory;@Beanpublic ItemReader<Reconciliation> reader() {// 模拟 100 万数据List<Reconciliation> data = new ArrayList<>();for (long i = 1; i <= 1_000_000; i++) {Reconciliation recon = new Reconciliation();recon.setId(i);recon.setAccountId("ACC" + i);recon.setAmount(new BigDecimal("100.00"));recon.setReconDate(LocalDate.now());data.add(recon);}return new ListItemReader<>(data);}@Beanpublic ItemProcessor<Reconciliation, Reconciliation> processor() {return item -> {// 简单处理return item;};}@Beanpublic ItemWriter<Reconciliation> writer() {JpaItemWriter<Reconciliation> writer = new JpaItemWriter<>();writer.setEntityManagerFactory(entityManagerFactory);return writer;}@Beanpublic Step importReconStep() {DtpExecutor executor = DtpRegistry.getExecutor("batchImportPool");return stepBuilderFactory.get("importReconStep").<Reconciliation, Reconciliation>chunk(1000) // 小批量提交.reader(reader()).processor(processor()).writer(writer()).taskExecutor(executor).throttleLimit(4) // 限制并发.build();}@Beanpublic Job importReconJob() {return jobBuilderFactory.get("importReconJob").start(importReconStep()).build();}
    }
    
  5. 控制器ReconController.java):

    package com.example.demo.controller;import com.example.demo.service.ReconciliationService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;@RestController
    public class ReconController {@Autowiredprivate ReconciliationService reconciliationService;@PostMapping("/import")public String startImport() {reconciliationService.startImportJob();return "Batch import started";}
    }
    
  6. AOP 切面BatchMonitoringAspect.java):

    package com.example.demo.aspect;import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;@Aspect
    @Component
    public class BatchMonitoringAspect {private static final Logger logger = LoggerFactory.getLogger(BatchMonitoringAspect.class);@Pointcut("execution(* com.example.demo.service.ReconciliationService.*(..))")public void serviceMethods() {}@Before("serviceMethods()")public void logMethodEntry() {logger.info("Entering batch service method");}@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")public void logException(Exception ex) {logger.error("Batch error: {}", ex.getMessage());}
    }
    
  7. 死锁重试机制(已集成在 ReconciliationService)。

  8. 运行并验证

    • 启动 MySQL 和 ActiveMQ。
    • 启动应用:mvn spring-boot:run
    • 触发导入:
      curl -X POST http://localhost:8081/import
      
      • 确认数据分片存储到 recon_db_0.reconciliation_0, recon_db_0.reconciliation_1, 等。
      • 检查 ActiveMQ 日志。
      • 访问 /actuator/threadpool 监控线程池状态。
    • 检查 MySQL 死锁日志:
      SHOW ENGINE INNODB STATUS;
      
2.2.2 原理
  • 分库分表:ShardingSphere 按 ID 哈希分片,分散锁竞争。
  • 小批量事务:Spring Batch 每 1000 条提交一次,减少锁时间。
  • 动态线程池:Dynamic TP 限制并发(4 个线程),避免过多事务。
  • 死lock 重试:检测死锁(MySQL 错误码 1213),自动重试。
  • AOP:记录性能和异常,便于定位。
2.2.3 优点
  • 显著降低死锁概率。
  • 高性能导入(100 万数据约 5-10 分钟)。
  • 动态调整线程池,优化资源。
2.2.4 缺点
  • 配置复杂,需熟悉 Spring Batch 和 ShardingSphere。
  • 重试机制可能增加延迟。
  • 分片查询需优化。
2.2.5 适用场景
  • 大数据量批量导入。
  • 高并发对账系统。
  • 分布式数据库优化。

2.3 集成先前查询

结合分页、Swagger、ActiveMQ、Spring Profiles、Spring Security、FreeMarker、热加载、ThreadLocal、Actuator 安全性、CSRF、WebSockets、异常处理、Web 标准、AOP、动态线程池、分库分表。

2.3.1 配置步骤
  1. 分页与排序

    • 添加分页查询:
      @Service
      public class ReconciliationService {@Autowiredprivate ReconciliationRepository reconciliationRepository;public Page<Reconciliation> searchRecon(String accountId, int page, int size, String sortBy, String direction) {try {CONTEXT.set("Query-" + Thread.currentThread().getName());Sort sort = Sort.by(Sort.Direction.fromString(direction), sortBy);PageRequest pageable = PageRequest.of(page, size, sort);return reconciliationRepository.findAll(pageable); // 简化示例} finally {CONTEXT.remove();}}
      }
      
  2. Swagger

    • 添加 Swagger 文档:
      @RestController
      @Tag(name = "对账管理", description = "对账数据导入和查询")
      public class ReconController {@Operation(summary = "触发批量导入")@PostMapping("/import")public String startImport() {reconciliationService.startImportJob();return "Batch import started";}@Operation(summary = "分页查询对账数据")@GetMapping("/reconciliations")public Page<Reconciliation> searchRecon(@RequestParam(defaultValue = "") String accountId,@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "10") int size,@RequestParam(defaultValue = "id") String sortBy,@RequestParam(defaultValue = "asc") String direction) {return reconciliationService.searchRecon(accountId, page, size, sortBy, direction);}
      }
      
  3. ActiveMQ

    • 已记录导入日志。
  4. Spring Profiles

    • 配置 application-dev.ymlapplication-prod.yml
      # application-dev.yml
      spring:shardingsphere:props:sql-show: truedynamic-tp:executors:- thread-pool-name: batchImportPoolcore-pool-size: 4max-pool-size: 8queue-capacity: 1000
      logging:level:root: DEBUG
      
      # application-prod.yml
      spring:shardingsphere:props:sql-show: falsedynamic-tp:executors:- thread-pool-name: batchImportPoolcore-pool-size: 8max-pool-size: 16queue-capacity: 2000
      logging:level:root: INFO
      
  5. Spring Security

    • 保护 API:
      @Configuration
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/import", "/reconciliations").authenticated().requestMatchers("/actuator/health").permitAll().requestMatchers("/actuator/**").hasRole("ADMIN").anyRequest().permitAll()).httpBasic().and().csrf().ignoringRequestMatchers("/ws");return http.build();}@Beanpublic UserDetailsService userDetailsService() {var user = User.withDefaultPasswordEncoder().username("admin").password("admin").roles("ADMIN").build();return new InMemoryUserDetailsManager(user);}
      }
      
  6. FreeMarker

    • 对账管理页面:
      @Controller
      public class WebController {@Autowiredprivate ReconciliationService reconciliationService;@GetMapping("/web/reconciliations")public String getReconciliations(@RequestParam(defaultValue = "") String accountId,@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "10") int size,Model model) {Page<Reconciliation> reconPage = reconciliationService.searchRecon(accountId, page, size, "id", "asc");model.addAttribute("reconciliations", reconPage.getContent());return "reconciliations";}
      }
      
      <!-- src/main/resources/templates/reconciliations.ftl -->
      <!DOCTYPE html>
      <html lang="zh-CN">
      <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>对账管理</title>
      </head>
      <body><h1>对账数据</h1><table><tr><th>ID</th><th>账户ID</th><th>金额</th><th>日期</th></tr><#list reconciliations as recon><tr><td>${recon.id}</td><td>${recon.accountId?html}</td><td>${recon.amount}</td><td>${recon.reconDate}</td></tr></#list></table>
      </body>
      </html>
      
  7. 热加载

    • 已启用 DevTools。
  8. ThreadLocal

    • 已清理 ThreadLocal(见 ReconciliationService)。
  9. Actuator 安全性

    • 已限制 /actuator/**
  10. CSRF

    • WebSocket 端点禁用 CSRF。
  11. WebSockets

    • 实时推送导入状态:
      @Controller
      public class WebSocketController {@Autowiredprivate SimpMessagingTemplate messagingTemplate;@MessageMapping("/import-status")public void sendImportStatus() {messagingTemplate.convertAndSend("/topic/import", "Batch import running");}
      }
      
  12. 异常处理

    • 处理死锁异常(已集成重试机制)。
  13. Web 标准

    • FreeMarker 模板遵循语义化 HTML。
  14. 动态线程池

    • 已使用 Dynamic TP 优化并发。
  15. 分库分表

    • 已集成 ShardingSphere。
  16. 运行并验证

    • 开发环境
      java -jar demo.jar --spring.profiles.active=dev
      
      • 触发导入,验证无死锁。
      • 检查分片表数据分布。
      • 监控 /actuator/threadpool 和 WebSocket 推送。
    • 生产环境
      java -jar demo.jar --spring.profiles.active=prod
      
      • 确认安全性、线程池配置。
2.3.2 原理
  • 分页:ShardingSphere 聚合跨库结果。
  • Swagger:文档化导入 API。
  • ActiveMQ:异步记录日志。
  • Profiles:控制线程池和日志级别。
  • Security:保护导入操作。
  • Batch:小批量事务降低死锁。
  • FreeMarker:渲染查询结果。
  • WebSockets:推送导入状态。
2.3.3 优点
  • 高效导入,消除死锁。
  • 集成 Spring Boot 生态。
  • 动态优化性能。
2.3.4 缺点
  • 配置复杂,需多组件协调。
  • 跨库查询需优化。
  • 重试增加少量延迟。
2.3.5 适用场景
  • 高并发批处理。
  • 大数据量对账。
  • 分布式系统优化。

三、性能与适用性分析

3.1 性能影响

  • 批量导入:100 万数据约 5-10 分钟(4 线程,1000 条/chunk)。
  • 死锁重试:每次重试增加 100-300ms。
  • 查询:50ms(1000 条,跨库)。
  • WebSocket 推送:2ms/消息。

3.2 性能测试

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BatchImportTest {@Autowiredprivate TestRestTemplate restTemplate;@Testpublic void testImportPerformance() {long startTime = System.currentTimeMillis();restTemplate.postForEntity("/import", null, String.class);long duration = System.currentTimeMillis() - startTime;System.out.println("Batch import: " + duration + " ms");}
}

测试结果(Java 17,8 核 CPU,16GB 内存):

  • 导入:约 300,000ms(100 万数据)。
  • 重试:0-3 次/导入。
  • 查询:50ms。

结论:优化后死锁显著减少,性能稳定。

3.3 适用性对比

方法死锁概率性能适用场景
单事务导入小数据量
分批+分库分表大数据量、高并发
云数据库云原生应用

四、常见问题与解决方案

  1. 问题1:死锁仍发生

    • 场景:高并发下死锁频繁。
    • 解决方案
      • 进一步降低 chunk 大小(如 500)。
      • 减少线程数(如 2)。
  2. 问题2:导入性能慢

    • 场景:100 万数据耗时过长。
    • 解决方案
      • 增加分片库/表数量。
      • 优化索引,移除冗余。
  3. 问题3:ThreadLocal 泄漏

    • 场景/actuator/threaddump 显示泄漏。
    • 解决方案
      • 确认 ThreadLocal 清理。
  4. 问题4:跨库查询慢

    • 场景:分页查询性能低。
    • 解决方案
      • 添加缓存(如 Redis)。
      • 优化分片键。

五、总结

通过分库分表(ShardingSphere)、小批量事务(Spring Batch)、动态线程池(Dynamic TP)和死锁重试机制,显著降低了批量导入 100 万对账数据的死锁问题。示例集成分页、Swagger、ActiveMQ、Profiles、Security、FreeMarker、WebSockets、AOP 等,性能稳定(5-10 分钟导入)。针对您的查询(ThreadLocal、Actuator、热加载、CSRF),通过清理、Security 和 DevTools 解决。

相关文章:

  • 一、对linux驱动文件编写时结构认识与记录
  • gpu硬件,gpu驱动,cuda,CUDA Toolkit,cudatoolkit,cudnn,nvcc概念解析
  • 操作系统 第2章节 进程,线程和作业
  • 【PhysUnits】3.3 SI 基础量纲单位(units/base.rs)
  • Leetcode刷题 由浅入深之字符串——541. 反转字符串Ⅱ
  • 振动临近失效状态,怎么频谱会是梳子?
  • API请求参数有哪些?
  • Modbus RTU 详解 + FreeMODBUS移植(附项目源码)
  • 【算法】:滑动窗口
  • 常见图像融合算法(alpha和金字塔融合)
  • 使用智能表格做FMEDA
  • Mysql--基础知识点--91.1--慢查询日志
  • 日常知识点之随手问题整理(思考单播,组播,广播哪个更省带宽)
  • RocketMQ 深度解析:架构设计与最佳实践
  • 学习黑客认识数字取证与事件响应(DFIR)
  • 修改docker为国内源
  • 【笔记】BCEWithLogitsLoss
  • NVME / DoCA 是什么?
  • 2025年 全新 AI 编程工具 Cursor 安装使用教程
  • 【RAG官方大神笔记】检索增强生成 (RAG):Python AI 教程的详细介绍
  • 当创业热土遇上年轻气息,上海南汇新城发展如何再发力?
  • 国家主席习近平会见斯洛伐克总理菲佐
  • 三星“七天机”质保期内屏幕漏液被要求自费维修,商家:系人为损坏
  • 从上海首个到成片复制,闵行零工市场如何优化劳动就业服务?
  • 经彩申城!上海网络大V沙龙活动走进闵行
  • 光大华夏:近代中国私立大学遥不可及的梦想