Wireshark 分析SQL 批量插入慢的问题

有一个数据导入程序需要导入大量的数据,使用 Spring JdbcTemplate 的批量操作功能进行数据批量导入,但是发现性能非常差,和普通的单条 SQL 执行性能差不多。

创建一个表:

sql 复制代码
CREATE TABLE `testuser` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

使用 JdbcTemplatebatchUpdate 方法,批量插入 10000 条记录到 testuser 表。

java 复制代码
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class JDBCTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Test
    public void test() {

        long begin = System.currentTimeMillis();

        String sql = "INSERT INTO `testuser` (`name`) VALUES (?)";

        //使用JDBC批量更新
        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

            @Override
            public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {

                log.info("批次:{}", i);

                //第一个参数(索引从1开始),也就是name列赋值
                preparedStatement.setString(1, "usera" + i);
            }

            @Override
            public int getBatchSize() {
                //批次大小为10000
                return 10000;
            }
        });

        log.info("cost : {} ms", System.currentTimeMillis() - begin);
    }
}

执行程序后可以看到,1 万条数据插入耗时 93 秒:

bash 复制代码
2024-11-16 22:41:20.688  INFO 18043 --- [main] com.redis.demo.JDBCTest                  : cost : 93678 ms

对于批量操作,我们希望程序可以把多条 insert SQL 语句合并成一条,或至少是一次性提交多条语句到数据库,以减少和 MySQL 交互次数、提高性能。

那么,我们的程序是这样运作的吗?

打开 Wireshark,启动后选择需要捕获的网卡(因为我连接的是远程服务器的 MySQL,这里选择 utun4,如果是本地,选择loopback

Wireshark是一个非常流行的网络封包分析工具,它能够捕获各种网络数据包并显示其详细信息。

然后,Wireshark 捕捉这个网卡的所有网络流量,在上方的显示过滤栏输入 tcp.port == 3306,来过滤出所有 3306 端口的 TCP 请求。

可以看到,程序运行期间和 MySQL 有大量交互。

因为 Wireshark 直接把 TCP 数据包解析为了 MySQL 协议,所以下方窗口可以直接显示 MySQL 请求的 SQL 查询语句。

我们看到,testuser 表的每次 insert 操作,插入的都是一行记录:

这就说明,我们的程序并不是在做批量插入操作,和普通的单条循环插入没有区别。

调试程序进入PreparedStatement 源码查看。

红线中判断了 rewriteBatchedStatements 参数是否为 true,是才会开启批量的优化。

优化方式有 2 种:

  • 优先把 insert 语句优化为一条语句,也就是 executeBatchedInserts 方法;
  • 再尝试把 insert 语句优化为多条语句一起提交,也就是 executePreparedBatchAsMultiStatement 方法。

实现批量提交优化的关键,在于 rewriteBatchedStatements 参数,我们修改连接字符串,并将其值设置为 true。

java 复制代码
spring.datasource.url=jdbc:mysql://ip:3306/db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false&rewriteBatchedStatements=true

重新按照之前的步骤打开 Wireshark 验证,可以看到:

  • 这次的 insert 语句被拼装成了一个 SQL;
  • 这个 TCP 包因为太大被分割成了 113 个片段传输,#402 请求是最后一个片段,其实际内容是 insert 语句的最后一部分内容。

为了查看整个 TCP 连接的所有数据包,可以在请求上点击右键,选择 Follow->TCP Stream

可以看到从 MySQL 认证开始到 insert 语句的所有数据包的内容:

查看最开始的握手数据包可以发现,TCP 的最大分段大小(MSS)是 1424 字节,而我们的 MySQL 超长 insert 的数据一共 138933 字节,因此被分成了 113 段传输,其中最大的一段是 1360 字节,低于 MSS 要求的 1424 字节。

最后可以看到插入 1 万条数据仅耗时 1915 毫秒,性能提升了 50 倍:

bash 复制代码
2024-11-16 20:49:53.875  INFO 12157 --- [main] com.redis.demo.JDBCTest                  : took : 1915 ms

参考资料:

《Java 业务开发错误 100 例》

相关推荐
网管NO.11 天前
SQL 排序分页精讲!ORDER BY+LIMIT 全套用法,报表分页
数据库·sql
我是一颗柠檬1 天前
【MySQL全面教学】MySQL基础SQL语句Day3(2026年)
数据库·后端·sql·mysql·oracle
XS0301061 天前
MyBatis动态SQL
数据库·sql·mybatis
hef2882 天前
SQL和Python怎么选?数据分析工具实战指南
python·sql·数据分析
顾凌陵2 天前
SQL注入漏洞进阶篇
网络·sql
ZengLiangYi2 天前
ChatCrystal大量对话导入时的内存优化
sql·ai编程
BossFriday2 天前
WhatsApp抓包分析
wireshark·ssl·信息与通信
ZuuuuYao2 天前
告别臃肿Postman ,国产 API 工具Reqable基于Flutter框架开发
测试工具·抓包·api工具·reqable