**高并发、微服务 、性能调优实战案例100讲，所有案例均源于个人工作实战，均配合代码落地**

加我微信：itsoku，所有案例均提供在线答疑。



# 第76节 千万级数据，全表update的正确姿势

千万数据全表更新，可能会导致严重的生产事故，本文将带大家分析下，千万级数据更新存在的问题，以及如何解决。

先收藏点赞，慢慢看。

<span style="font-weight:bold; color:red">目前整个课程59块钱，100个案例，含所有源码 & 文档 & 技术支持，可点击左下角小黄车了解</span>



# 1、准备千万数据

## 1）创建测试表

```sql
create table t_demo (
	id bigint auto_increment PRIMARY key,
	c1 bigint
);
```

## 2）插入千万数据

下面对代码会向t_demo表插入1000万数据，可能需要稍等会。

```java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class BatchInsertExample {

    public static void main(String[] args) throws SQLException {
        String jdbcUrl = "jdbc:mysql://localhost:3306/luren?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8";
        String username = "root";
        String password = "root123";

        Connection connection = null;
        PreparedStatement preparedStatement = null;

        try {
            // 建立数据库连接
            connection = DriverManager.getConnection(jdbcUrl, username, password);

            // SQL插入语句，使用占位符来防止SQL注入
            String sql = "INSERT INTO t_demo(c1) VALUES (?)";

            // 创建PreparedStatement对象，并设置为批处理模式
            preparedStatement = connection.prepareStatement(sql);
            for (int j = 0; j < 2000; j++) {
                connection.setAutoCommit(false); // 设置为手动提交

                long c1 = System.currentTimeMillis();
                // 循环添加多个批处理操作
                for (int i = 0; i < 5000; i++) {
                    // 设置占位符的值
                    preparedStatement.setLong(1, c1 + 1);
                    // 添加到批处理中
                    preparedStatement.addBatch();
                }

                // 执行批处理
                int[] batchResults = preparedStatement.executeBatch();
                System.out.println(batchResults.length);
                // 手动提交事务
                connection.commit();
            }

        } catch (SQLException e) {
            e.printStackTrace();
            // 发生异常时回滚事务
            if (connection != null) {
                connection.rollback();
            }
        } finally {
            // 关闭PreparedStatement和Connection
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (connection != null) {
                connection.close();
            }
        }
    }
}
```

## 3）千万数据准备完毕

```java
mysql> select count(*) from t_demo;
+----------+
| count(*) |
+----------+
| 10075001 |
+----------+
1 row in set (1.92 sec)
```



# 2、千万数据全表更新存在的问题

## 1）影响系统性能

- **执行时间长**：全表更新是一个极其耗时的操作，因为数据库需要遍历表中的每一条记录并进行更新，对于千万级的数据表，这个过程可能需要数小时甚至更长时间
- **资源消耗大**：全表更新会大量消耗数据库服务器的CPU、内存和I/O资源，导致服务器性能下降，可能影响其他数据库操作的响应时间和吞吐量
- **锁表问题**：在更新过程中，数据库可能会对表进行锁定，以防止数据不一致，这会导致其他用户或进程无法访问该表，从而影响业务的正常运行

## 2）导致主从同步延迟严重

全表更新会一次性在主库上产生大量binlog日志，而主从同步是依靠binlog来完成的，此时就会导致主从同步延迟特别严重。

比如系统采用了读写分离，某个用户在界面上更新了一个数据，操作完成后，去另外一个页面查看操作结果，而这个页面的数据是从从库获取的，由于主从延迟严重，用户此时看到的结果和期望的不一致，会感觉莫名其妙。

## 3）可能导致系统崩溃

更新过程中会导致锁表，此时如果有大量其他请求也去更新此表的数据，都会被阻塞，进而可能会耗光tomcat的线程，导致系统无法继续对外提供服务，引起连锁反应，导致整个系统崩溃。



# 4、演示下全表更新：会锁表，耗时特别久

### 窗口1中

全表更新千万数据，此操作执行过程中，会对整个表的数据上锁

```sql
update t_demo set c1 = c1 + 1;
```

### 窗口2中

按主键更新1条数据，由于窗口1中对全表上锁了，窗口2中这个操作会被阻塞

```sql
update t_demo set c1 = c1+1 where id = 1;
```

![image-20240820111023625](img/image-20240820111023625.png)



# 5、正确的更新姿势

```java
//获取表中最小的id
long minId = select min(id) from t_demo;

//循环批量更新数据
while(true){
	//查询出1000条需要更新的记录的id，注意查询条件(id>=#{minId})，这个查询会走主键索引，查询会特别快，毫秒级别
	List<Long> idList = select id from t_demo where id>=#{minId} limit 1000;
	
	//如果数据为空，说明没有需要更新的数据了，跳出循环
	if(idList为空){
		break;
	}
	
	//根据id批量更新数据,这里更新1000条数据，也会非常快，事务也很小，也是毫秒级别
	update t_demo set 更新的字段 where id in (#{idList[0]},#{idList[1]},...,#{idList[n]});
	
	//minId = idList最后一条记录的id
	minId = idList.get(idList.size()-1);
}
```



# 获取，源码 & 文档 & 技术支持

需要的小伙伴可以加我微信：itsoku，获取。



# 高并发 & 微服务 & 性能调优实战案例100讲

## 已更新 76 节课

<span style="font-weight:bold; color:red">目前整个课程59块钱，含所有源码 & 文档 & 技术支持，一杯咖啡的价格，还没下手的朋友，赶紧了</span>。

```java
1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
24. 一行代码搞定系统操作日志
25. Aop简化MyBatis分页功能
26. ThreadLocal 遇到线程池有大坑 & 通用解决方案
27. SpringBoot读写分离实战（一个注解搞定读写分离 && 强制路由主库）
28. MQ专题-MQ典型的使用场景
29. MQ专题-如何确保消息的可靠性
30. MQ专题-SpringBoot中，手把手教你实现事务消息
31. 手写一个好用的延迟任务处理工具类
32. MQ专题-MQ延迟消息通用方案实战
33. MQ消息幂等消费 & 消费失败衰减式重试通用方案 & 代码 & 文档
34. MQ专题：顺序消息通用方案实战 & 代码落地 & 文档
35. MQ专题：消息积压相关问题及解决思路
36. 分布式事务-MQ最终一致性-实现跨库转账（案例+源码+文档）
37. 分布式事务-MQ最终一致性-实现电商账户余额提现到微信钱包（案例+源码+文档）
38. 分布式事务：通用的TCC分布式事务生产级代码落地实战
39. 分布式锁详解
40. 分享一个特别好用的Redissson分布式锁工具类
41. 一个注解轻松搞定分布式锁
42. 微服务中如何传递公共参数？
43. 接口幂等，通用方案 & 代码落地
44. 微服务链路日志追踪实战
45. 接口测试利器HTTP Client，不用Postman也可以
46. 封装MyBatis，实现通用无SQL版CRUD功能ORM框架
47. MyBatisPlus 轻松实现多租户数据隔离
48. 电商系统-资金账户表设计 及 应用实战
49. UML画图神器：PlantUML，画图效率飞升
50. 多线程事务，3秒插入百万数据
51. SpringBoot中自动初始化数据库功能，非常好用
52. SpringBoot优雅停机
53. 分享一个特好用的集合工具类，开发效率轻松翻倍
54. 性能调优：线程死锁相关问题
55. 如何排查OOM？
56. cpu飙升，如何快速排查？
57. cpu飙升，使用Arthas，3秒定位问题
58. 接口响应慢，使用Arthas，3秒定位问题代码
59. 策略模式，轻松消除ifelse代码
60. 生产上，代码未生效，如何排查？
61. 使用MySQL，实现一个高性能，分布式id生成器
62. 方法执行异常，使用arthas，快速定位问题
63. 扫码登录详解
64. 使用hutool生成&解析二维码，太方便了
65. SpringBoot中，redis中实现排行榜
66. SpringBoot中，Redis如何实现查找附近的人功能？
67. SpringBoot中，接口签名，通用方案，一次性搞懂
68. SpringBoot中，接口加解密，通用方案实战
69. 分库、分表、分库分表，如何选择？
70. 分库分表：分表字段如何选择？
71. 分库分表：分表数量为什么建议是2的n次方？
72. 分库分表：如何平滑迁移数据？
73. 并发编程有多难？值得反复研究的一个案例
74. 使用Redis Pipeline，接口性能提升10倍
75. 电商中，重复支付如何解决？
76. 千万级数据，全表update的正确姿势
```



## 课程部分大纲，连载中。。。。

以下课程均来源于个人多年的实战，均提供原理讲解 && 源码落地

1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
24. 一行代码搞定系统操作日志
25. Aop简化MyBatis分页功能
26. ThreadLocal 遇到线程池有大坑 & 通用解决方案
27. SpringBoot读写分离实战（一个注解搞定读写分离 && 强制路由主库）
28. MQ专题：MQ典型的7种使用场景
29. MQ专题：如何确保消息的可靠性
30. MQ专题：SpringBoot中，手把手教你实现事务消息
31. 手写一个好用的延迟任务处理工具类
32. MQ专题：延迟消息通用方案实战
33. MQ专题：消息幂等消费 & 消费失败自动重试通用方案 & 代码落地
34. MQ专题：顺序消息通用方案实战
35. MQ专题：消息积压问题
36. 分布式事务-MQ最终一致性-实现跨库转账（案例+源码+文档）
37. 分布式事务-MQ最终一致性-实现电商账户余额提现到微信钱包（案例+源码+文档）
38. 分布式事务：通用的TCC分布式事务生产级代码落地实战
39. 分布式锁详解
40. 分享一个特别好用的Redissson分布式锁工具类
41. 分布式锁：一个注解轻松实现布式锁
42. 微服务中如何传递上下文？实战
43. 接口幂等，通用方案 & 代码落地
44. 微服务链路日志追踪实战
45. 接口测试利器HTTP Client，不用Postman也可以
46. 封装MyBatis，实现通用无SQL版CRUD功能
47. MyBatisPlus 轻松实现 多租户数据隔离
48. 电商系统-资金账户表设计 及 应用实战
49. 开发者必须掌握的一款UML画图工具，画图效率飞升
50. 多线程事务，3秒插入百万数据
51. SpringBoot自动初始化数据库功能，太好用了
52. SpringBoot优雅停机
53. 分享一个特别好用的集合工具类，开发效率大幅提升
54. 性能调优：如何排查死锁？
55. 如何排查OOM？
56. cpu飙升，如何快速排查？
57. cpu飙升，使用Arthas，3秒定位问题
58. 接口响应慢，使用Arthas，3秒定位问题代码
59. 策略模式，轻松消除ifelse代码
60. 生产上，代码未生效，如何排查？
61. 使用MySQL，实现一个高性能，分布式id生成器
62. 方法执行异常，使用arthas，快速定位问题
63. 扫码登录详解
64. 使用hutool生成&解析二维码，太方便了
65. SpringBoot中，Redis如何实现排行榜功能？
66. SpringBoot中，Redis如何实现查找附近的人功能？
67. SpringBoot中，接口签名，通用方案，一次性搞懂
68. SpringBoot中，接口加解密，通用方案实战
69. 分库、分表、分库分表，如何选择？
70. 分库分表：分表字段如何选择？
71. 分库分表：分表数量为什么建议是2的n次方？
72. 分库分表：如何平滑迁移数据？
73. 并发编程有多难？值得反复研究的一个案例
74. 使用Redis Pipeline，接口性能提升10倍
75. 电商系统中，如何解决重复支付？
76. 千万级数据，全表update的正确姿势
77. 更多实战案例详解