引言
在当今互联网应用中,高并发场景已经成为常态。无论是电商大促、社交网络热点事件,还是金融交易系统,MySQL作为最流行的开源关系型数据库,都面临着巨大的性能挑战。当并发请求量激增时,数据库往往成为系统瓶颈,导致响应延迟飙升甚至服务不可用。本文将从连接池优化、查询优化、索引设计、架构扩展等多个维度,全面解析MySQL在高并发环境下的性能瓶颈与实战解决方案。
一、连接池优化:高效管理数据库连接
1.1 连接池的重要性
在高并发场景下,频繁创建和销毁数据库连接会消耗大量系统资源,导致性能下降。连接池通过预先创建并维护一定数量的数据库连接,实现连接的复用,显著降低连接建立的开销。
1.2 主流连接池对比
| 连接池 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| HikariCP | 性能最优,轻量级,延迟低 | 配置相对复杂 | Java应用,对性能要求极高 |
| Druid | 功能丰富,监控完善,支持SQL防火墙 | 相对较重,配置项多 | 需要监控和安全控制的场景 |
| C3P0 | 配置简单,稳定 | 性能一般,已逐渐淘汰 | 老旧系统维护 |
1.3 HikariCP实战配置示例
// HikariCP配置示例
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 核心参数配置
config.setMaximumPoolSize(20); // 最大连接数
config.setMinimumIdle(5); // 最小空闲连接数
config.setConnectionTimeout(30000); // 连接超时时间(ms)
config.setIdleTimeout(600000); // 空闲连接超时时间(ms)
config.setMaxLifetime(1800000); // 连接最大存活时间(ms)
config.setLeakDetectionThreshold(5000); // 连接泄漏检测阈值(ms)
// 性能优化参数
config.setConnectionTestQuery("SELECT 1"); // 连接测试SQL
config.setValidationTimeout(3000); // 验证超时时间
config.setInitializationFailTimeout(1); // 初始化失败超时
HikariDataSource dataSource = new HikariDataSource(config);
1.4 连接池参数调优指南
最大连接数(maximumPoolSize):
- 计算公式:
最大连接数 = (核心数 × 2) + 有效磁盘数 - 实际测试:通过压力测试观察连接数与吞吐量的关系
- 示例:4核CPU,SSD磁盘,建议最大连接数10-20
最小空闲连接数(minimumIdle):
- 建议设置为最大连接数的1/4到1/3
- 避免设置过小导致突发流量时连接创建延迟
连接超时时间(connectionTimeout):
- 生产环境建议30秒
- 开发环境可适当缩短
连接泄漏检测(leakDetectionThreshold):
- 建议设置为5000ms(5秒)
- 及时发现未关闭的连接
1.5 连接池监控与诊断
-- 查看MySQL当前连接数
SHOW STATUS LIKE 'Threads_connected';
-- 查看连接数峰值
SHOW STATUS LIKE 'Max_used_connections';
-- 查看连接线程状态
SHOW PROCESSLIST;
-- 查看连接超时设置
SHOW VARIABLES LIKE '%timeout%';
监控指标:
- 活跃连接数 vs 最大连接数
- 连接等待时间
- 连接泄漏次数
- 连接创建/销毁频率
二、查询优化:从慢查询到高效执行
2.1 慢查询日志分析
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超过1秒的查询记录
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
2.2 使用EXPLAIN分析执行计划
-- 示例:分析一个复杂查询
EXPLAIN
SELECT u.username, o.order_id, o.total_amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2023-01-01'
AND o.status = 'completed'
AND o.total_amount > 1000
ORDER BY o.created_at DESC
LIMIT 10;
EXPLAIN输出解析:
- type:访问类型(ALL > index > range > ref > eq_ref > const > system)
- key:实际使用的索引
- rows:预估扫描行数
- Extra:额外信息(Using filesort, Using temporary等)
2.3 常见查询优化技巧
2.3.1 避免SELECT *
-- 不推荐:查询所有列
SELECT * FROM users WHERE id = 1;
-- 推荐:只查询需要的列
SELECT id, username, email FROM users WHERE id = 1;
2.3.2 分页优化
-- 不推荐:深度分页性能差
SELECT * FROM orders ORDER BY created_at DESC LIMIT 1000000, 10;
-- 推荐:使用子查询优化
SELECT o.*
FROM orders o
JOIN (SELECT id FROM orders ORDER BY created_at DESC LIMIT 1000000, 10) tmp
ON o.id = tmp.id;
-- 更优:使用游标分页(适用于按时间排序)
SELECT * FROM orders
WHERE created_at < '2023-12-01 00:00:00'
ORDER BY created_at DESC
LIMIT 10;
2.3.3 避免在WHERE子句中使用函数
-- 不推荐:索引失效
SELECT * FROM users WHERE DATE(created_at) = '2023-01-01';
-- 推荐:使用范围查询
SELECT * FROM users
WHERE created_at >= '2023-01-01 00:00:00'
AND created_at < '2023-01-02 00:00:00';
2.4 批量操作优化
-- 不推荐:逐条插入
INSERT INTO users (username, email) VALUES ('user1', 'user1@example.com');
INSERT INTO users (username, email) VALUES ('user2', 'user2@example.com');
-- ... 1000次插入
-- 推荐:批量插入
INSERT INTO users (username, email) VALUES
('user1', 'user1@example.com'),
('user2', 'user2@example.com'),
-- ... 1000条记录
('user1000', 'user1000@example.com');
-- 批量更新
UPDATE users
SET status = CASE id
WHEN 1 THEN 'active'
WHEN 2 THEN 'inactive'
-- ...
WHEN 1000 THEN 'pending'
END
WHERE id IN (1, 2, ..., 1000);
三、索引设计:性能优化的核心
3.1 索引类型与选择
| 索引类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| B-Tree索引 | 等值查询、范围查询 | 通用,支持排序 | 不适合全文搜索 |
| 哈希索引 | 等值查询 | 查询速度极快 | 不支持范围查询,仅Memory引擎 |
| 全文索引 | 文本搜索 | 支持模糊匹配 | 仅MyISAM/InnoDB,性能一般 |
| 空间索引 | 地理位置查询 | 支持空间数据 | 使用较少 |
| 联合索引 | 多条件查询 | 减少索引数量 | 需遵循最左前缀原则 |
3.2 索引设计原则
3.2.1 最左前缀原则
-- 创建联合索引
CREATE INDEX idx_user_order ON orders(user_id, order_date, status);
-- 有效使用索引的查询
SELECT * FROM orders WHERE user_id = 1; -- ✅ 使用索引
SELECT * FROM orders WHERE user_id = 1 AND order_date = '2023-01-01'; -- ✅ 使用索引
SELECT * FROM orders WHERE user_id = 1 AND order_date = '2023-01-01' AND status = 'completed'; -- ✅ 使用索引
-- 无效使用索引的查询
SELECT * FROM orders WHERE order_date = '2023-01-01'; -- ❌ 无法使用索引
SELECT * FROM orders WHERE status = 'completed'; -- ❌ 无法使用索引
SELECT * FROM orders WHERE order_date = '2023-01-01' AND status = 'completed'; -- ❌ 无法使用索引
3.2.2 覆盖索引
-- 创建覆盖索引
CREATE INDEX idx_cover ON orders(user_id, order_date, total_amount, status);
-- 查询只需要索引中的列,无需回表
SELECT user_id, order_date, total_amount, status
FROM orders
WHERE user_id = 1 AND order_date >= '2023-01-01';
-- 执行计划中会显示"Using index"
3.2.3 索引选择性
-- 计算列的选择性(唯一值数量/总行数)
SELECT
COUNT(DISTINCT status) / COUNT(*) AS status_selectivity,
COUNT(DISTINCT user_id) / COUNT(*) AS user_id_selectivity
FROM orders;
-- 选择性高的列(接近1)适合建索引
-- 选择性低的列(如性别、状态)不适合单独建索引
3.3 索引维护与优化
3.3.1 索引碎片整理
-- 查看索引碎片率
SELECT
table_name,
index_name,
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb,
ROUND(data_length / NULLIF(index_length, 0), 2) AS ratio
FROM information_schema.tables
WHERE table_schema = 'mydb'
ORDER BY ratio DESC;
-- 重建索引(在线DDL,MySQL 5.6+)
ALTER TABLE orders ENGINE = InnoDB;
-- 或使用OPTIMIZE TABLE(会锁表)
OPTIMIZE TABLE orders;
3.3.2 索引监控
-- 查看索引使用情况
SELECT
table_name,
index_name,
rows_selected,
rows_inserted,
rows_updated,
rows_deleted
FROM information_schema.table_statistics
WHERE table_schema = 'mydb';
-- 查看未使用的索引(MySQL 8.0+)
SELECT * FROM sys.schema_unused_indexes;
四、表结构设计优化
4.1 数据类型选择
-- 不推荐:使用过大的数据类型
CREATE TABLE users (
id BIGINT, -- 过度使用
username VARCHAR(255), -- 过长
age INT, -- 可以用TINYINT
created_at DATETIME -- 可以用TIMESTAMP
);
-- 推荐:根据实际需求选择
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT, -- 4字节,足够
username VARCHAR(50), -- 根据实际最大长度
age TINYINT UNSIGNED, -- 0-255足够
created_at TIMESTAMP -- 4字节,支持时区
);
4.2 分区表设计
-- 按时间范围分区
CREATE TABLE logs (
id INT AUTO_INCREMENT,
log_time DATETIME,
log_level VARCHAR(20),
message TEXT,
PRIMARY KEY (id, log_time)
) PARTITION BY RANGE (YEAR(log_time) * 100 + MONTH(log_time)) (
PARTITION p202301 VALUES LESS THAN (202302),
PARTITION p202302 VALUES LESS THAN (202303),
PARTITION p202303 VALUES LESS THAN (202304),
PARTITION p202304 VALUES LESS THAN (202305),
PARTITION p202305 VALUES LESS THAN (202306),
PARTITION p202306 VALUES LESS THAN (202307),
PARTITION p202307 VALUES LESS THAN (202308),
PARTITION p202308 VALUES LESS THAN (202309),
PARTITION p202309 VALUES LESS THAN (202310),
PARTITION p202310 VALUES LESS THAN (202311),
PARTITION p202311 VALUES LESS THAN (202312),
PARTITION p202312 VALUES LESS THAN (202401),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- 查询时自动分区裁剪
SELECT * FROM logs WHERE log_time BETWEEN '2023-05-01' AND '2023-05-31';
-- 只扫描p202305分区
4.3 垂直分表与水平分表
垂直分表:
-- 原表:users(包含过多列)
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
phone VARCHAR(20),
address TEXT,
-- ... 20+个字段
created_at TIMESTAMP
);
-- 拆分后
CREATE TABLE users_base (
id INT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
created_at TIMESTAMP
);
CREATE TABLE users_profile (
user_id INT PRIMARY KEY,
phone VARCHAR(20),
address TEXT,
-- ... 其他扩展字段
FOREIGN KEY (user_id) REFERENCES users_base(id)
);
水平分表:
-- 按用户ID取模分表
CREATE TABLE orders_0 (
id INT PRIMARY KEY,
user_id INT,
-- ... 其他字段
INDEX idx_user (user_id)
);
CREATE TABLE orders_1 (
id INT PRIMARY KEY,
user_id INT,
-- ... 其他字段
INDEX idx_user (user_id)
);
-- 应用层路由逻辑
int tableIndex = userId % 2;
String tableName = "orders_" + tableIndex;
五、架构扩展方案
5.1 读写分离
// Spring Boot配置示例
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource routingDataSource() {
DynamicDataSource routingDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(masterDataSource());
return routingDataSource;
}
}
// 自定义路由数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
// 数据源上下文管理
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
5.2 主从复制配置
# 主库配置(my.cnf)
[mysqld]
server-id = 1
log_bin = mysql-bin
binlog_format = ROW
expire_logs_days = 7
sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
# 从库配置
[mysqld]
server-id = 2
relay_log = mysql-relay-bin
read_only = 1
log_slave_updates = 1
-- 主库创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
-- 查看主库状态
SHOW MASTER STATUS;
-- 从库配置复制
CHANGE MASTER TO
MASTER_HOST='master_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=1234;
-- 启动复制
START SLAVE;
SHOW SLAVE STATUS\G
5.3 分库分表中间件
ShardingSphere配置示例:
# sharding.yaml
dataSources:
ds_0: !!com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/db_0
username: root
password: password
ds_1: !!com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/db_1
username: root
password: password
shardingRule:
tables:
orders:
actualDataNodes: ds_${0..1}.orders_${0..3}
tableStrategy:
inline:
shardingColumn: user_id
algorithmExpression: orders_${user_id % 4}
databaseStrategy:
inline:
shardingColumn: user_id
algorithmExpression: ds_${user_id % 2}
bindingTables:
- orders
defaultDatabaseStrategy:
inline:
shardingColumn: user_id
algorithmExpression: ds_${user_id % 2}
六、监控与调优实战
6.1 性能监控指标
-- 关键性能指标查询
SELECT
-- 连接相关
VARIABLE_VALUE AS threads_connected,
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Max_used_connections') AS max_connections,
-- 查询相关
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Questions') AS total_queries,
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Queries') AS queries_per_second,
-- 缓冲池相关
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_pages_total') AS buffer_pool_pages,
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_pages_data') AS buffer_pool_data,
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_pages_free') AS buffer_pool_free,
-- 锁相关
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_row_lock_waits') AS row_lock_waits,
(SELECT VARIABLE_VALUE FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_row_lock_time_avg') AS avg_lock_time
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Threads_connected';
6.2 慢查询分析工具
# 使用pt-query-digest分析慢查询日志
pt-query-digest /var/log/mysql/slow.log --since="2023-01-01" --until="2023-01-31"
# 输出示例:
# Query 1: 12.5 QPS, 150ms avg, 10000 total
# SELECT * FROM orders WHERE user_id = ? AND status = ?
# Time range: 2023-01-01 to 2023-01-31
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 100 10000
# Exec time 95 1500s 100ms 500ms 150ms 250ms 50ms 140ms
# Rows sent 1 10000 1 1 1 1 0 1
# Rows examine 95 10000000 1000 1000 1000 1000 0 1000
# Query size 1 1000 100 100 100 100 0 100
6.3 性能调优案例
案例:电商大促期间订单查询缓慢
问题现象:
- 订单查询接口响应时间从50ms飙升至2s
- 数据库CPU使用率超过80%
- 慢查询日志显示大量
SELECT * FROM orders WHERE user_id = ? AND status = 'pending'
分析过程:
-- 1. 查看执行计划
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 'pending';
-- 输出:
-- +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
-- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-- +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
-- | 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 100M | Using where |
-- +----+-------------+--------+------+---------------+------+---------+------+------+-------------+
-- 2. 查看表结构
SHOW CREATE TABLE orders;
-- 发现:没有(user_id, status)联合索引
解决方案:
-- 1. 创建联合索引
CREATE INDEX idx_user_status ON orders(user_id, status);
-- 2. 优化查询语句(只查询需要的列)
SELECT order_id, order_date, total_amount
FROM orders
WHERE user_id = 123 AND status = 'pending'
ORDER BY order_date DESC
LIMIT 20;
-- 3. 添加覆盖索引
CREATE INDEX idx_user_status_cover ON orders(user_id, status, order_date, total_amount, order_id);
-- 4. 验证优化效果
EXPLAIN SELECT order_id, order_date, total_amount
FROM orders
WHERE user_id = 123 AND status = 'pending'
ORDER BY order_date DESC
LIMIT 20;
-- 输出:
-- +----+-------------+--------+------+------------------------+------------------------+---------+-------------+------+-------------+
-- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-- +----+-------------+--------+------+------------------------+------------------------+---------+-------------+------+-------------+
-- | 1 | SIMPLE | orders | ref | idx_user_status_cover | idx_user_status_cover | 5 | const,const | 100 | Using where |
-- +----+-------------+--------+------+------------------------+------------------------+---------+-------------+------+-------------+
优化效果:
- 响应时间:2s → 10ms
- CPU使用率:80% → 20%
- 扫描行数:100M → 100
七、高并发场景下的特殊优化
7.1 高并发写入优化
-- 1. 使用批量插入
INSERT INTO logs (user_id, action, timestamp) VALUES
(1, 'login', '2023-01-01 10:00:00'),
(2, 'logout', '2023-01-01 10:01:00'),
-- ... 1000条记录
(1000, 'purchase', '2023-01-01 10:02:00');
-- 2. 调整事务大小
-- 不推荐:大事务(10000条记录)
BEGIN;
INSERT INTO orders (...) VALUES (...); -- 10000次
COMMIT;
-- 推荐:小事务(100条记录/事务)
BEGIN;
INSERT INTO orders (...) VALUES (...); -- 100次
COMMIT;
-- 重复100次
-- 3. 使用INSERT DELAYED(MySQL 5.6+已废弃,推荐使用其他方案)
-- 4. 使用内存表缓冲
CREATE TABLE temp_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
action VARCHAR(50),
timestamp DATETIME
) ENGINE=MEMORY;
-- 批量写入内存表
INSERT INTO temp_logs (...) VALUES (...);
-- 定时批量写入磁盘表
INSERT INTO logs SELECT * FROM temp_logs;
TRUNCATE TABLE temp_logs;
7.2 高并发读取优化
-- 1. 使用查询缓存(MySQL 8.0已移除,推荐使用外部缓存)
-- 2. 使用覆盖索引
CREATE INDEX idx_cover ON orders(user_id, order_date, total_amount, status);
-- 3. 使用索引提示
SELECT * FROM orders USE INDEX (idx_user_status)
WHERE user_id = 123 AND status = 'pending';
-- 4. 使用延迟关联
SELECT * FROM orders o
JOIN (SELECT id FROM orders WHERE user_id = 123 AND status = 'pending' ORDER BY order_date DESC LIMIT 20) tmp
ON o.id = tmp.id;
7.3 锁优化
-- 1. 减少锁竞争
-- 不推荐:长事务
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
-- ... 其他业务逻辑
COMMIT;
-- 推荐:短事务
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
COMMIT;
-- 其他业务逻辑在事务外
-- 2. 使用乐观锁
ALTER TABLE orders ADD COLUMN version INT DEFAULT 0;
-- 更新时检查版本
UPDATE orders
SET status = 'shipped', version = version + 1
WHERE order_id = 123 AND version = 0;
-- 3. 使用行级锁提示
SELECT * FROM orders WHERE user_id = 123 FOR UPDATE;
八、实战案例:电商系统高并发优化
8.1 系统架构
用户请求 → Nginx负载均衡 → 应用服务器集群 → 连接池 → MySQL主从集群
↓
Redis缓存层
8.2 关键优化点
1. 连接池配置:
// HikariCP配置
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(50); // 根据压测调整
config.setMinimumIdle(10);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
config.setLeakDetectionThreshold(5000);
2. 索引设计:
-- 订单表索引
CREATE INDEX idx_user_status ON orders(user_id, status);
CREATE INDEX idx_order_date ON orders(order_date);
CREATE INDEX idx_user_order_date ON orders(user_id, order_date);
-- 用户表索引
CREATE INDEX idx_user_email ON users(email);
CREATE INDEX idx_user_phone ON users(phone);
3. 查询优化:
// 使用Redis缓存热点数据
public Order getOrderById(Long orderId) {
String cacheKey = "order:" + orderId;
String cached = redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return JSON.parseObject(cached, Order.class);
}
// 查询数据库
Order order = orderMapper.selectById(orderId);
if (order != null) {
redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(order), 300, TimeUnit.SECONDS);
}
return order;
}
// 使用批量查询
public List<Order> getOrdersByUserIds(List<Long> userIds) {
if (userIds.isEmpty()) return Collections.emptyList();
// 使用IN查询,但注意IN列表长度
String sql = "SELECT * FROM orders WHERE user_id IN (:userIds)";
Map<String, Object> params = new HashMap<>();
params.put("userIds", userIds);
return orderMapper.selectByMap(params);
}
4. 读写分离:
// 动态数据源切换
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 写操作使用主库
@Transactional
@DataSource("master")
public void createOrder(Order order) {
orderMapper.insert(order);
}
// 读操作使用从库
@DataSource("slave")
public Order getOrder(Long orderId) {
return orderMapper.selectById(orderId);
}
}
8.3 压测与监控
压测脚本示例:
# 使用ab进行压测
ab -n 10000 -c 100 -H "Content-Type: application/json" \
-p order.json \
http://localhost:8080/api/orders
# 使用JMeter进行复杂场景压测
# 1. 创建线程组:100线程,循环100次
# 2. 添加HTTP请求:下单接口
# 3. 添加响应断言:检查返回状态码
# 4. 添加聚合报告:查看吞吐量、响应时间
监控面板:
-- 实时监控SQL
SELECT
ps.id AS process_id,
ps.user AS user,
ps.host AS host,
ps.db AS database,
ps.command AS command,
ps.time AS time_sec,
ps.state AS state,
LEFT(ps.info, 100) AS query_preview
FROM information_schema.processlist ps
WHERE ps.command != 'Sleep'
ORDER BY ps.time DESC
LIMIT 20;
九、总结与最佳实践
9.1 高并发优化检查清单
连接池优化:
- [ ] 配置合适的连接池大小
- [ ] 设置合理的超时时间
- [ ] 启用连接泄漏检测
- [ ] 监控连接池使用情况
查询优化:
- [ ] 开启慢查询日志
- [ ] 定期分析慢查询
- [ ] 避免SELECT *
- [ ] 优化分页查询
- [ ] 使用批量操作
索引设计:
- [ ] 遵循最左前缀原则
- [ ] 创建覆盖索引
- [ ] 定期清理无用索引
- [ ] 监控索引使用情况
表结构优化:
- [ ] 选择合适的数据类型
- [ ] 考虑分区表
- [ ] 适时进行垂直/水平分表
架构扩展:
- [ ] 实现读写分离
- [ ] 配置主从复制
- [ ] 考虑分库分表
- [ ] 引入缓存层
监控与调优:
- [ ] 建立性能监控体系
- [ ] 定期进行压力测试
- [ ] 建立性能基线
- [ ] 制定应急预案
9.2 常见误区与注意事项
- 过度优化:不要在数据量小的时候过早优化,保持代码简洁
- 索引滥用:每个索引都有维护成本,不要为每个查询都建索引
- 忽视监控:没有监控的优化是盲目的,建立完善的监控体系
- 单点故障:高并发系统必须考虑容灾和故障转移
- 忽视业务:技术优化要服务于业务,不能脱离业务场景
9.3 未来趋势
- 云原生数据库:如Amazon Aurora、Google Cloud Spanner等
- HTAP数据库:如TiDB、OceanBase等,支持混合事务/分析处理
- AI驱动的优化:自动索引推荐、智能查询优化
- Serverless数据库:按需扩展,自动管理
结语
MySQL高并发优化是一个系统工程,需要从连接管理、查询优化、索引设计、架构扩展等多个维度综合考虑。本文提供的实战指南涵盖了从基础到高级的优化策略,但实际应用中还需要根据具体业务场景进行调整。记住,优化是一个持续的过程,需要不断地监控、分析和改进。通过合理的优化,MySQL完全能够支撑千万级并发的业务场景,为应用提供稳定、高效的数据库服务。
