大数据量更新某个字段的值如何提高效率.

为响应安全等保要求,本文分享了一种高效的数据库批量加密更新方案。通过将数据按月分批加载至内存,利用SqlBulkCopy批量插入及优化的更新语句,显著提升处理速度,避免业务长时间中断。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

近期由于安全等保测评要求必须提高数据库信息的安全等级, 需要对已有数据进行加密处理… 表W_Report 有1千万左右的数据, 如果用ado.net 批量查出来一批,for循环内存中逐条加密后再update回去的方式. 初步测试了一下每秒 21 条.

这种肯定 是最原始的方案,1千万条估算了下要80小时, 全部处理完要3天. 升级过程中得停止系统. 业务停3天是不可能的. 再加上其它的服务器硬件给力, 算每秒100条, 最起码也要停16个小时. 这肯定不行.

毛主席教导我们: 只要脑袋不熄火,办法总比困难多.

下面采用了新的方案批量处理,

新方案的思路是
第一步:将数据按月份分批批量加载到系统内存中,一次性加载1个月的. 大概有20万条数据. 使用Ado.net 读取到 DataReader,
第二步:逐行处理,在内存中计算加密后的数据,然后保存到DataTable
第三步:使用SqlBulkCopy 批量将加密后的DataTable 插入到一张临时表
第四步:使用Update语句一次性将临时表的数据更新到老表
第五步:清空临时表中的数据, 准备处理下一个月的数据.

此方案每一步的隐患都很多
第一步:不知道加载时间多少.
第二步:少不了的计算时间
第三步:SqlBulkCopy 批量插入10万条数据据说只要1-2秒.
第四步:Update语句用一个表去更新另外一个表的性能不知如何.
第五步:清空临时表,可以使用直接删表的方式, 速度会很快.秒秒钟.

为了不做无用功, 先测验第四步的性能如何:
1.先取一个月的数据插入到临时表当作测试用. 共取了 157375条数据 用时不到1秒
这里只取了3个需要加密的字段和主键.

select  Id, PatientName,PatientIDCard,PatientPhone  into TempReportNewData from W_Report WHERE ReportTime  BETWEEN '2019-11-01' and '2019-11-30' 

第一次测试. 无主键 再更新回去到老表 用时 157375条数据 272.486s 平均每秒578条数据

update W_Report 
set 
PatientName = tmp.PatientName,
PatientIDCard = tmp.PatientIDCard,
PatientPhone = tmp.PatientPhone
from TempReportNewData as tmp
where W_Report.Id = tmp.Id

第二次测试. 加主键后 再更新回去 用时 157375条数据 279.281s 平均每秒576条数据
比原来的还慢了点, 影响不大.先不管了, 当作没变化.

第三次测试,. 在主表的更新条件中加时间索引字段作为条件.157375条数据 用时 4.055s秒
我当时就傻眼了. 这么快的速度.

update W_Report 
set 
PatientName = tmp.PatientName,
PatientIDCard = tmp.PatientIDCard,
PatientPhone = tmp.PatientPhone
from TempReportNewData as tmp
where W_Report.Id = tmp.Id 
and W_Report.ReportTime  BETWEEN '2019-11-01' and '2019-11-30'

至此,第四步的效率从一定的程度上来看,还是可以接受的,
时间范围半个月应该更快, 因为有时间索引做过滤条件可以大幅提高更新速度. 毕竟时间索引可以跳过好多数据的排查过程. 灵活运用索引很有必要.
在这次的测试过后,又冒出来一个新的想法, 可以先把前面的3步提前做了.先逐条计算好放到临时表中. 然后等升级切换的时候再批量分批一个月一个月的更新到W_report
那就是分分钟的事情了. 毕竟这些3个月之前的老数据这些字段的数据是不会变的. 可以提前做.

明天再继续测试 取数据的效率和加密的效率.

反正步骤1,2,3可以提前做, 慢点也无所谓, 压力不大了.

又过了一天.经过同事测试,发现我昨天的写的代码效率变低了. 1000多条就要50多秒, 这是为何?
后来同事排查了一下发现

UPDATE W_Report
SET PatientName = tmp.PatientName,
 PatientIDCard = tmp.PatientIDCard,
 PatientPhone = tmp.PatientPhone
FROM
 TempReportNewData AS tmp
WHERE
 W_Report.Id = tmp.Id
AND W_Report.ReportTime BETWEEN '2019-09-27 00:00:00' AND '2019-09-28 00:00:00'
AND tmp.ReportTime BETWEEN '2019-09-27 00:00:00' AND '2019-09-28 00:00:00'

他的语句中加了时分秒, 然后就慢了好几个数量级.

经过测试前面3步 按天从数据库读出来,再计算,然后再批量使用SqlBulkCopy 插入,经过测试34万条数据用了30秒. 速度也是非常快的.

<think>我们正在讨论若依框架(RuoYi)中如何批量更新某个字段。根据引用内容,我们知道若依框架内置了代码生成工具,并且整合了MyBatis-Plus,而MyBatis-Plus提供了强大的CRUD操作功能。在MyBatis-Plus中,批量更新操作可以通过使用`updateBatchById`方法实现,但需要注意的是,该方法默认情况下需要配置事务管理,并且要求主键存在。此外,还可以通过自定义SQL的方式实现批量更新。结合若依框架的结构,通常我们会通过Service层来调用批量更新的方法。以下是一个示例步骤:1.在Service接口中定义批量更新方法。2.在ServiceImpl中实现该方法,可以使用MyBatis-Plus提供的方法或自定义Mapper方法。假设我们有一个实体类`Student`,对应数据库表`my_student`,我们要批量更新`status`字段。###示例代码####1.Mapper接口```java@MapperpublicinterfaceStudentMapperextendsBaseMapper<Student>{//如果使用MyBatis-Plus的BaseMapper,默认已经提供了一些方法,但批量更新需要自定义//可以在此处定义批量更新的方法intupdateBatch(@Param("list")List<Student>list);}```####2.MapperXML文件在对应的MapperXML文件中,编写批量更新的SQL语句:```xml<updateid="updateBatch"><foreachcollection="list"item="item"separator=";">UPDATEmy_studentSETstatus=#{item.status}WHEREid=#{item.id}</foreach></update>```注意:上述SQL语句使用了动态SQL的foreach标签,但这种方法在MySQL中需要支持多条语句执行(在连接字符串中添加`allowMultiQueries=true`)。另一种方式是使用CaseWhen的方式:```xml<updateid="updateBatch">UPDATEmy_studentSETstatus=<foreachcollection="list"item="item"open="CASEid"close="END">WHEN#{item.id}THEN#{item.status}</foreach>WHEREidIN<foreachcollection="list"item="item"open="("separator=","close=")">#{item.id}</foreach></update>```####3.Service接口```javapublicinterfaceIStudentServiceextendsIService<Student>{booleanupdateBatch(List<Student>list);}```####4.Service实现```java@ServicepublicclassStudentServiceImplextendsServiceImpl<StudentMapper,Student>implementsIStudentService{@Override@Transactional(rollbackFor=Exception.class)//添加事务publicbooleanupdateBatch(List<Student>list){returnthis.baseMapper.updateBatch(list)>0;}}```####5.使用事务确保在SpringBoot启动类上添加`@EnableTransactionManagement`(若依框架默认已配置),并且在方法上使用`@Transactional`注解。###注意事项-批量操作时,注意SQL语句的长度,避免一次性更新太多数据导致数据库性能问题。-使用事务保证操作的原子性。-在MySQL中,使用CaseWhen方式更新比多条语句执行效率更高。另外,如果更新的是同一个字段为同一个,可以使用以下方式:```java//在Service中publicbooleanupdateStatusByIds(List<Long>ids,Stringstatus){LambdaUpdateWrapper<Student>updateWrapper=newLambdaUpdateWrapper<>();updateWrapper.set(Student::getStatus,status).in(Student::getId,ids);returnthis.update(updateWrapper);}```这样就不用写MapperXML,直接使用MyBatis-Plus的条件构造器即可。###总结根据需求选择合适的方法:-如果需要批量更新的每条记录字段不同,则使用自定义的批量更新SQL(如CaseWhen方式)。-如果批量更新的是同一个字段为同一个,则使用MyBatis-Plus的UpdateWrapper更加方便。§§相关问题§§1.在若依框架中,如何配置事务以保证批量更新的原子性?2.使用MyBatis-Plus的UpdateWrapper批量更新有什么注意事项?3.除了上述方法,若依框架还有哪些支持批量操作的方式?</think> ### 若依框架批量更新字段方法 在若依框架中批量更新字段,可以通过 **MyBatis-Plus 的 UpdateWrapper** 或 **BaseMapper 的批量更新方法** 实现。以下是具体方法和示例代码: --- #### 🛠️ 方法一:使用 UpdateWrapper(推荐) 适用于更新多个实体到相同的字段: ```java // ServiceImpl 实现类中 @Transactional(rollbackFor = Exception.class) public int batchUpdateStatus(List<Long> ids, String status) { UpdateWrapper<Student> updateWrapper = new UpdateWrapper<>(); updateWrapper .set("status", status) // 设置要更新字段 .in("id", ids); // WHERE id IN (...) return studentMapper.update(null, updateWrapper); } ``` --- #### 🧩 方法二:批量执行更新语句 使用 MyBatis-Plus 的批量操作方法: ```java // ServiceImpl 实现类中 @Transactional(rollbackFor = Exception.class) public boolean batchUpdate(List<Student> list) { return studentMapper.updateBatchById(list) > 0; } // 调用示例 List<Student> students = students.stream() .map(s -> { s.setStatus("ACTIVE"); // 设置新 return s; }).collect(Collectors.toList()); batchUpdate(students); ``` --- #### 📌 关键注意事项 1. **事务控制** 必须添加 `@Transactional` 注解确保原子性 ```java @Transactional(rollbackFor = Exception.class) // 关键注解 ``` 2. **字段映射配置** 确认实体类字段名与数据库列名一致(或通过 `@TableField` 注解映射): ```java @TableField("status") // 数据库字段名 private String userStatus; ``` 3. **批量大小限制** 建议单次更新数据量不超过 1000 条,可分割执行: ```java List<List<Long>> partitions = Lists.partition(idList, 500); partitions.forEach(part -> batchUpdate(part, "ACTIVE")); ``` 4. **索引优化** 确保 WHERE 条件的字段(如 `id`)有索引,避免全表扫描 --- #### 🔍 完整调用示例 ```java // Controller 层 @PostMapping("/batchUpdateStatus") public AjaxResult batchUpdate(@RequestBody BatchUpdateDTO dto) { return success(studentService.batchUpdateStatus(dto.getIds(), dto.getStatus())); } // DTO 对象 @Data public class BatchUpdateDTO { private List<Long> ids; private String status; } ``` 📌 **最佳实践建议**:优先使用 `UpdateWrapper` 方式(方法一),它会在数据库层面生成单条 SQL 语句,效率更高: ```sql /* 实际执行SQL */ UPDATE my_student SET status = ? WHERE id IN (?,?,...); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值