测试背景与目标 在现代应用系统中,随着业务数据量的快速增长,数据库性能成为系统性能的关键瓶颈。特别是在处理百万级数据量时,数据库的CRUD(创建、读取、更新、删除)操作的性能表现直接影响用户体验和系统稳定性。
本次测试旨在通过实际验证,分析在单表100万数据量下,不同技术方案对数据库操作性能的影响,为技术选型和性能优化提供数据支撑。
测试环境配置 硬件环境
CPU :Intel Core i7-10700K,8核16线程
内存 :32GB DDR4-3200
硬盘 :1TB NVMe SSD(读写速度3500MB/s)
网络 :千兆以太网
软件环境
操作系统 :Windows 10 Pro 64位
JDK版本 :OpenJDK 11.0.12
数据库 :MySQL 8.0.26
连接池 :HikariCP 4.0.3
ORM框架 :MyBatis 3.5.7 + MyBatis-Plus 3.4.3
数据库配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CREATE TABLE user_performance_test ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR (50 ) NOT NULL , email VARCHAR (100 ) NOT NULL , age INT NOT NULL , create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP , update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP , INDEX idx_username (username), INDEX idx_email (email), INDEX idx_age (age) ); innodb_buffer_pool_size = 4 G innodb_log_file_size = 512 M max_connections = 1000
测试方案设计 测试数据准备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Data @AllArgsConstructor @NoArgsConstructor public class UserTestData { private Long id; private String username; private String email; private Integer age; private LocalDateTime createTime; private LocalDateTime updateTime; } public class DataGenerator { public static List<UserTestData> generateTestData (int count) { List<UserTestData> users = new ArrayList <>(count); Random random = new Random (); for (int i = 0 ; i < count; i++) { UserTestData user = new UserTestData (); user.setUsername("user_" + String.format("%08d" , i)); user.setEmail("user_" + i + "@test.com" ); user.setAge(18 + random.nextInt(50 )); user.setCreateTime(LocalDateTime.now()); user.setUpdateTime(LocalDateTime.now()); users.add(user); } return users; } }
测试用例设计 1. 写入性能测试
批量插入 :测试不同批次大小对性能的影响
单条插入 :测试逐条插入的性能表现
JDBC批量插入 :测试JDBC原生批量插入性能
ORM批量插入 :测试MyBatis-Plus批量插入性能
2. 读取性能测试
主键查询 :基于ID的精确查询
索引查询 :基于username、email、age的索引查询
范围查询 :基于age的范围查询
分页查询 :大数据量下的分页性能
全表扫描 :无索引条件的查询性能
3. 更新性能测试
主键更新 :基于ID的单条更新
批量更新 :基于条件的批量更新
索引字段更新 :更新索引字段的性能影响
非索引字段更新 :更新非索引字段的性能表现
4. 删除性能测试
主键删除 :基于ID的单条删除
批量删除 :基于条件的批量删除
全表删除 :清空表的性能表现
连接池性能对比测试 测试场景 为了验证连接池对性能的影响,我们测试了以下三种方案:
方案A:无连接池(原生JDBC) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class JdbcDirectTest { private static final String URL = "jdbc:mysql://localhost:3306/test_db" ; private static final String USER = "root" ; private static final String PASSWORD = "password" ; public void testInsert () { String sql = "INSERT INTO user_performance_test (username, email, age) VALUES (?, ?, ?)" ; long startTime = System.currentTimeMillis(); try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) { PreparedStatement pstmt = conn.prepareStatement(sql); for (int i = 0 ; i < 1000000 ; i++) { pstmt.setString(1 , "user_" + i); pstmt.setString(2 , "user_" + i + "@test.com" ); pstmt.setInt(3 , 20 + i % 50 ); pstmt.executeUpdate(); } } long endTime = System.currentTimeMillis(); System.out.println("无连接池插入100万条耗时:" + (endTime - startTime) + "ms" ); } }
方案B:HikariCP连接池 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @Configuration public class HikariConfig { @Bean public DataSource dataSource () { HikariConfig config = new HikariConfig (); config.setJdbcUrl("jdbc:mysql://localhost:3306/test_db" ); config.setUsername("root" ); config.setPassword("password" ); config.setMaximumPoolSize(20 ); config.setMinimumIdle(5 ); config.setConnectionTimeout(30000 ); config.setIdleTimeout(600000 ); config.setMaxLifetime(1800000 ); config.setLeakDetectionThreshold(60000 ); return new HikariDataSource (config); } } @Service public class HikariPerformanceTest { @Autowired private DataSource dataSource; public void batchInsert (List<UserTestData> users) { String sql = "INSERT INTO user_performance_test (username, email, age) VALUES (?, ?, ?)" ; long startTime = System.currentTimeMillis(); try (Connection conn = dataSource.getConnection()) { PreparedStatement pstmt = conn.prepareStatement(sql); for (int i = 0 ; i < users.size(); i++) { UserTestData user = users.get(i); pstmt.setString(1 , user.getUsername()); pstmt.setString(2 , user.getEmail()); pstmt.setInt(3 , user.getAge()); pstmt.addBatch(); if (i % 1000 == 0 ) { pstmt.executeBatch(); pstmt.clearBatch(); } } pstmt.executeBatch(); } long endTime = System.currentTimeMillis(); System.out.println("HikariCP批量插入100万条耗时:" + (endTime - startTime) + "ms" ); } }
方案C:Druid连接池 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public class DruidConfig { @Bean public DataSource dataSource () { DruidDataSource dataSource = new DruidDataSource (); dataSource.setUrl("jdbc:mysql://localhost:3306/test_db" ); dataSource.setUsername("root" ); dataSource.setPassword("password" ); dataSource.setInitialSize(5 ); dataSource.setMaxActive(20 ); dataSource.setMinIdle(5 ); dataSource.setMaxWait(30000 ); dataSource.setTimeBetweenEvictionRunsMillis(60000 ); dataSource.setMinEvictableIdleTimeMillis(300000 ); return dataSource; } }
详细性能测试结果 1. 写入性能测试结果
测试场景
批次大小
耗时(ms)
TPS(条/秒)
内存使用(MB)
无连接池单条插入
1
2,850,000
351
512
HikariCP单条插入
1
1,200,000
833
256
HikariCP批量插入
100
45,000
22,222
128
HikariCP批量插入
1000
28,000
35,714
256
HikariCP批量插入
5000
22,000
45,455
512
Druid批量插入
1000
32,000
31,250
256
MyBatis-Plus批量
1000
35,000
28,571
384
结论分析 :
连接池对性能提升显著,HikariCP比无连接池提升约58%
批量插入是性能关键,批次大小1000-5000为最佳平衡点
HikariCP在性能上略优于Druid,但差异不大
2. 读取性能测试结果
查询类型
是否使用索引
平均耗时(ms)
并发QPS
备注
主键查询
是
0.8
1250
精确查询
username索引查询
是
1.2
833
等值查询
email索引查询
是
1.5
667
等值查询
age范围查询
是
45
22
查询10万条
分页查询(20条)
是
5
200
LIMIT 20
分页查询(100条)
是
12
83
LIMIT 100
全表扫描
否
8500
0.12
无WHERE条件
关键发现 :
索引对查询性能至关重要,有索引查询比无索引快1000倍以上
范围查询性能受数据量影响显著,需要合理设计分页策略
主键查询性能最优,适合高频精确查询场景
3. 更新性能测试结果
更新类型
影响行数
平均耗时(ms)
索引影响
备注
主键单条更新
1
2.5
无影响
直接定位
username条件更新
1
15
使用索引
等值匹配
age范围更新
10000
1200
使用索引
批量更新
非索引字段更新
1
3
无影响
更新普通字段
索引字段更新
1
8
需要维护索引
更新索引列
性能洞察 :
更新索引字段有额外开销,因为需要维护索引结构
批量更新性能与影响行数成正比,需要评估批量大小
主键更新性能稳定,适合高频单条更新场景
4. 删除性能测试结果
删除类型
影响行数
平均耗时(ms)
外键检查
备注
主键单条删除
1
3.2
无
精确删除
username条件删除
1
12
无
索引删除
age范围删除
10000
800
无
批量删除
全表删除
100万
15000
无
TRUNCATE TABLE
条件批量删除
50万
8500
无
DELETE … WHERE
索引对性能的影响分析 测试设计 为了验证索引的重要性,我们进行了对比测试:
有索引vs无索引性能对比
操作类型
有索引(ms)
无索引(ms)
性能提升倍数
精确查询
1.2
8500
7083倍
范围查询
45
12000
267倍
排序查询
25
9500
380倍
分组查询
180
25000
139倍
索引类型对比测试
索引类型
查询耗时(ms)
更新耗时(ms)
存储空间(MB)
无索引
8500
2.5
0
B-Tree索引
1.2
8.5
45
Hash索引
0.8
12
52
复合索引
0.9
10
65
优化建议 写入优化策略
批量插入 :使用批量插入替代单条插入,批次大小控制在1000-5000条
连接池配置 :合理配置连接池参数,HikariCP推荐配置:1 2 3 4 maximumPoolSize =20 minimumIdle =5 connectionTimeout =30000 idleTimeout =600000
事务管理 :合理使用事务,批量操作使用手动事务提交
索引优化 :写入密集型场景,考虑临时禁用索引,写入完成后重建
查询优化策略
索引设计 :
高频查询字段建立索引
复合索引遵循最左前缀原则
避免在索引列上使用函数
查询优化 :
使用覆盖索引减少回表
合理使用LIMIT限制结果集
避免SELECT *,只查询需要的列
分页优化 :
大数据量分页使用游标或延迟关联
避免使用OFFSET大数值分页
更新优化策略
批量更新 :将多条更新合并为批量更新
条件优化 :使用索引字段作为更新条件
避免索引字段更新 :减少索引维护开销
删除优化策略
分批删除 :大批量删除分批处理,避免长时间锁表
TRUNCATE替代 :全表删除使用TRUNCATE替代DELETE
归档策略 :历史数据定期归档,减少主表数据量
性能监控与诊断 监控指标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Component public class DatabasePerformanceMonitor { private final MeterRegistry meterRegistry; public void recordQueryTime (String queryType, long duration) { meterRegistry.timer("db.query.time" , "type" , queryType) .record(duration, TimeUnit.MILLISECONDS); } public void recordConnectionPoolMetrics (HikariDataSource dataSource) { HikariPoolMXBean poolMXBean = dataSource.getHikariPoolMXBean(); meterRegistry.gauge("db.pool.active" , poolMXBean, HikariPoolMXBean::getActiveConnections); meterRegistry.gauge("db.pool.idle" , poolMXBean, HikariPoolMXBean::getIdleConnections); } }
慢查询诊断 1 2 3 4 5 6 7 8 9 10 11 12 SET long_query_time = 1 ;SET slow_query_log = 'ON' ;SELECT * FROM mysql.slow_log WHERE start_time >= DATE_SUB(NOW(), INTERVAL 1 HOUR )ORDER BY query_time DESC ;EXPLAIN SELECT * FROM user_performance_test WHERE username LIKE '%user_99999%' ;
结论与建议 核心结论
连接池至关重要 :使用连接池可提升50-60%的性能,HikariCP是首选
批量操作是关键 :批量插入/更新比单条操作快20-40倍
索引不可或缺 :索引可将查询性能提升100-1000倍
批次大小优化 :批量操作的最佳批次大小为1000-5000条
内存和性能平衡 :需要在内存使用和性能之间找到平衡点
实际应用建议 开发规范
强制使用连接池 :所有数据库操作必须使用连接池
批量操作优先 :能批量就不单条,批次大小1000-5000
索引设计规范 :所有查询条件字段必须建立索引
性能测试 :所有SQL必须经过性能测试,响应时间<100ms
配置模板 1 2 3 4 5 6 7 8 9 10 11 12 spring.datasource.hikari.maximum-pool-size =20 spring.datasource.hikari.minimum-idle =5 spring.datasource.hikari.connection-timeout =30000 spring.datasource.hikari.idle-timeout =600000 spring.datasource.hikari.max-lifetime =1800000 spring.datasource.hikari.leak-detection-threshold =60000 mybatis-plus.global-config.db-config.insert-strategy =not_null mybatis-plus.configuration.default-executor-type =batch mybatis-plus.configuration.jdbc-type-for-null =null
通过本次测试,我们为百万级数据量的数据库操作提供了详实的性能数据和优化建议,这些结论可以直接应用于生产环境,确保系统在高数据量下仍能保持良好的性能表现。