1. 为查询缓存优化查询
详细解释:
在 MySQL 5.7 及以前版本中,查询缓存可以存储 SELECT 语句及其结果集。当完全相同的查询再次执行时,MySQL 直接从缓存返回结果,避免了解析、优化和执行的开销。
具体示例:
sql
-- 不会使用查询缓存的写法(因为 CURDATE() 是动态函数)
SELECT username FROM user WHERE signup_date >= CURDATE();
-- 会使用查询缓存的写法
SET @today = DATE_FORMAT(NOW(), '%Y-%m-%d');
SELECT username FROM user WHERE signup_date >= @today;
现代说明:
MySQL 8.0 已移除查询缓存,因为:
-
高并发下缓存失效竞争严重
-
现代应用更适合使用应用层缓存(如 Redis)
-
但理解此原理有助于编写更高效的 SQL
2. 使用 EXPLAIN 分析查询
详细解释:
EXPLAIN 显示 MySQL 如何执行查询,包括:
-
使用的索引
-
表连接顺序
-
扫描的行数
-
是否使用临时表
具体示例:
sql
-- 分析查询执行计划
EXPLAIN SELECT u.name, o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.country = 'China'
ORDER BY o.order_date DESC;
-- 输出结果示例:
-- id: 1
-- select_type: SIMPLE
-- table: u
-- type: ref
-- key: idx_country
-- rows: 1000
-- Extra: Using where; Using temporary; Using filesort
关键指标解读:
-
type :
const>eq_ref>ref>range>index>ALL -
key:实际使用的索引
-
rows:预估扫描行数(越小越好)
-
Extra :避免出现
Using filesort、Using temporary
3. 当只要一行数据时使用 LIMIT 1
详细解释:
明确告知优化器只需找到第一条匹配记录即可停止搜索。
具体示例:
sql
-- 检查用户是否存在(效率低)
SELECT * FROM users WHERE email = 'user@example.com';
-- 即使找到记录也会继续扫描
-- 检查用户是否存在(效率高)
SELECT 1 FROM users WHERE email = 'user@example.com' LIMIT 1;
-- 找到第一条匹配记录立即返回
性能对比:
sql
-- 表有 100万条数据,目标记录在第 10条
-- 无 LIMIT 1:扫描 10行
-- 有 LIMIT 1:扫描 1行
4. 为搜索字段建索引
详细解释:
索引就像书籍的目录,可以快速定位数据。
具体示例:
sql
-- 创建前(0.25秒)
SELECT COUNT(*) FROM users WHERE last_name LIKE 'Zhang%';
-- 全表扫描 100万行
-- 创建索引
ALTER TABLE users ADD INDEX idx_last_name (last_name);
-- 创建后(0.02秒)
SELECT COUNT(*) FROM users WHERE last_name LIKE 'Zhang%';
-- 只扫描 'Zhang' 开头的记录
索引类型选择:
sql
-- 单列索引
ALTER TABLE users ADD INDEX idx_email (email);
-- 复合索引(最左前缀原则)
ALTER TABLE users ADD INDEX idx_country_city (country, city);
-- 唯一索引
ALTER TABLE users ADD UNIQUE INDEX uk_email (email);
-- 全文索引(用于文本搜索)
ALTER TABLE articles ADD FULLTEXT INDEX ft_content (title, content);
5. 在 JOIN 时使用相同类型的字段
详细解释:
类型不匹配会导致索引失效,MySQL 需要隐式类型转换。
具体示例:
sql
-- 错误示例:类型不匹配
CREATE TABLE users (
id INT PRIMARY KEY,
state_code VARCHAR(10)
);
CREATE TABLE states (
state_code INT, -- 这里是 INT 类型
state_name VARCHAR(50)
);
-- JOIN 时索引失效(需要类型转换)
SELECT u.name, s.state_name
FROM users u
JOIN states s ON u.state_code = s.state_code; -- VARCHAR 与 INT 比较
-- 正确示例:类型一致
CREATE TABLE states (
state_code VARCHAR(10), -- 改为 VARCHAR
state_name VARCHAR(50),
INDEX idx_state_code (state_code)
);
字符集也要一致:
sql
-- 检查字符集
SHOW CREATE TABLE users;
SHOW CREATE TABLE companies;
-- 如果不一致,修改字符集
ALTER TABLE companies CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
6. 避免 ORDER BY RAND()
详细解释:
ORDER BY RAND() 会为每行生成随机数并排序,性能极差。
具体示例:
sql
-- 错误做法:性能灾难
SELECT username FROM users ORDER BY RAND() LIMIT 1;
-- 对 100万行数据每行调用 RAND() 并排序
-- 正确做法:应用层生成随机偏移量
-- 第一步:获取总行数
SELECT COUNT(*) AS total FROM users;
-- 第二步:应用层生成随机数(假设 total=1000000)
SET @random_offset = FLOOR(RAND() * 1000000);
-- 第三步:使用偏移量查询
SELECT username FROM users LIMIT @random_offset, 1;
更高效的随机采样:
sql
-- 对于大表,可以使用条件随机采样
SELECT username FROM users
WHERE id >= (SELECT FLOOR(MAX(id) * RAND()) FROM users)
LIMIT 1;
**7. 避免 SELECT ***
详细解释:
只选择需要的列可以减少:
-
网络传输量
-
内存占用
-
磁盘 I/O
具体示例:
sql
-- 不推荐:读取所有列
SELECT * FROM users WHERE user_id = 1;
-- 返回 id, username, email, password, created_at, updated_at, profile...
-- 推荐:只读取需要的列
SELECT username, email FROM users WHERE user_id = 1;
-- 只返回 username, email
真实性能对比:
sql
-- 用户表有 50个字段,但页面只需要用户名和头像
-- SELECT *:传输 10KB 数据
-- SELECT username, avatar:传输 2KB 数据
-- 节省 80% 的网络传输和内存占用
8. 永远为每张表设置 ID 主键
详细解释:
InnoDB 使用聚簇索引,数据按主键顺序存储。
具体示例:
sql
-- 错误做法:使用业务字段作为主键
CREATE TABLE users (
email VARCHAR(255) PRIMARY KEY, -- VARCHAR 主键性能差
username VARCHAR(50)
);
-- 正确做法:使用自增 INT 主键
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
username VARCHAR(50) NOT NULL,
INDEX idx_email (email)
);
主键选择的影响:
sql
-- UUID 主键 vs INT 主键
CREATE TABLE orders_uuid (
id CHAR(36) PRIMARY KEY, -- UUID,随机写入,页分裂严重
amount DECIMAL(10,2)
);
CREATE TABLE orders_int (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, -- 顺序写入,性能好
amount DECIMAL(10,2)
);
9. 使用 ENUM 代替 VARCHAR
详细解释:
ENUM 在内部存储为整数,但显示为字符串。
具体示例:
sql
-- VARCHAR 方式(每个值占用变长空间)
CREATE TABLE users (
id INT PRIMARY KEY,
gender VARCHAR(10), -- 'male', 'female', 'unknown'
status VARCHAR(20) -- 'active', 'inactive', 'pending', 'banned'
);
-- ENUM 方式(每个值只占 1-2 字节)
CREATE TABLE users (
id INT PRIMARY KEY,
gender ENUM('male', 'female', 'unknown') NOT NULL,
status ENUM('active', 'inactive', 'pending', 'banned') NOT NULL
);
存储空间对比:
sql
-- 100万条记录
-- VARCHAR(10) 性别字段:平均 5字节 × 100万 = 5MB
-- ENUM 性别字段:1字节 × 100万 = 1MB
-- 节省 80% 存储空间
10. 从 PROCEDURE ANALYSE() 获取建议
详细解释:
分析表中实际数据分布,给出数据类型优化建议。
具体示例:
sql
-- 获取字段优化建议
SELECT * FROM users PROCEDURE ANALYSE(256, 1024);
-- 输出示例:
-- Field_name: users.email
-- Min_value: a@test.com
-- Max_value: zzz@example.org
-- Min_length: 8
-- Max_length: 32
-- Empties_or_zeros: 0
-- Nulls: 0
-- Avg_value_or_avg_length: 18.23
-- Std: NULL
-- Optimal_fieldtype: VARCHAR(32) NOT NULL
实际应用:
sql
-- 根据建议优化表结构
ALTER TABLE users
MODIFY COLUMN email VARCHAR(32) NOT NULL,
MODIFY COLUMN age TINYINT UNSIGNED NOT NULL;
11. 尽可能使用 NOT NULL
详细解释:
NULL 需要额外存储空间和特殊处理逻辑。
具体示例:
sql
-- 允许 NULL 的表
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
description TEXT, -- 可能为 NULL
price DECIMAL(10,2) -- 可能为 NULL
);
-- 使用 NOT NULL 和默认值
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT NOT NULL DEFAULT '',
price DECIMAL(10,2) NOT NULL DEFAULT 0.00
);
查询性能影响:
sql
-- NULL 值查询需要特殊处理
SELECT * FROM products WHERE description IS NULL;
SELECT * FROM products WHERE description IS NOT NULL;
-- NOT NULL 字段查询更简单
SELECT * FROM products WHERE description = '';
12. 使用 Prepared Statements
详细解释:
预处理语句提供性能和安全性双重好处。
具体示例:
java
import java.sql.*;
public class UserDAO {
public String getUserByEmailAndStatus(String email, String status) {
String username = null;
// 使用 try-with-resources 自动关闭资源
try (Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "username", "password");
PreparedStatement stmt = conn.prepareStatement(
"SELECT username FROM users WHERE email = ? AND status = ?")) {
// 绑定参数
stmt.setString(1, email);
stmt.setString(2, status);
// 执行并获取结果
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
username = rs.getString("username");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return username;
}
}
性能优势:
sql
-- 相同结构的查询只需要解析一次
-- 第一次:解析、优化、执行
-- 后续:直接使用缓存的执行计划
13. 无缓冲查询
详细解释:
逐行获取结果,不等待所有数据加载到内存。
具体示例:
java
import java.sql.*;
public class StreamingPreparedStatement {
public void processLargeTableWithCondition(String condition) {
String url = "jdbc:mysql://localhost:3306/test?useCursorFetch=true";
try (Connection conn = DriverManager.getConnection(url, "username", "password");
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM large_table WHERE status = ?",
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY)) {
// 设置参数
stmt.setString(1, condition);
// 启用流式读取
stmt.setFetchSize(Integer.MIN_VALUE);
try (ResultSet rs = stmt.executeQuery()) {
// 逐行处理
while (rs.next()) {
Map<String, Object> row = new HashMap<>();
ResultSetMetaData metaData = rs.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
String columnName = metaData.getColumnName(i);
Object value = rs.getObject(i);
row.put(columnName, value);
}
// 处理整行数据
processRow(row);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
private void processRow(Map<String, Object> row) {
// 处理单行数据
System.out.println("Processing row: " + row);
}
}
适用场景:
-
处理大型结果集(如导出数据)
-
内存有限的服务器环境
-
需要快速获取第一行数据的场景
14. IP 地址存为 UNSIGNED INT
详细解释:
IP地址本质是32位无符号整数。
具体示例:
sql
-- 创建表
CREATE TABLE access_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
ip_address INT UNSIGNED NOT NULL, -- 存储为整数
access_time DATETIME,
INDEX idx_ip (ip_address)
);
-- 插入数据(字符串转整数)
INSERT INTO access_logs (ip_address, access_time)
VALUES (INET_ATON('192.168.1.100'), NOW());
-- 查询数据(整数转字符串)
SELECT id, INET_NTOA(ip_address) AS ip, access_time
FROM access_logs
WHERE ip_address BETWEEN INET_ATON('192.168.1.0') AND INET_ATON('192.168.1.255');
存储空间对比:
sql
-- VARCHAR(15):15字节
-- INT UNSIGNED:4字节
-- 节省 73% 存储空间
15. 固定长度的表更快
详细解释:
定长记录可以快速计算偏移量,随机访问性能好。
具体示例:
sql
-- 变长表(包含 VARCHAR, TEXT, BLOB)
CREATE TABLE user_profiles (
id INT PRIMARY KEY,
username VARCHAR(50),
bio TEXT, -- 变长字段
avatar BLOB -- 变长字段
);
-- 定长表(所有字段长度固定)
CREATE TABLE user_stats (
id INT PRIMARY KEY,
login_count INT,
last_login DATE, -- 固定3字节
status TINYINT -- 固定1字节
);
性能优势:
sql
-- 计算第N条记录的位置:
-- 定长表:offset = (N-1) × record_size
-- 变长表:需要遍历前面的所有记录
16. 垂直分割
详细解释:
将宽表按列拆分为多个窄表。
具体示例:
sql
-- 原始宽表(经常查询和更新的字段混在一起)
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(255),
email VARCHAR(100),
last_login DATETIME, -- 频繁更新,会清空查询缓存
bio TEXT, -- 很少读取的大字段
preferences JSON -- 很少读取的复杂字段
);
-- 垂直分割后
CREATE TABLE users_core ( -- 核心表,频繁访问
id INT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(255),
email VARCHAR(100)
);
CREATE TABLE users_activity ( -- 活动表,频繁更新
user_id INT PRIMARY KEY,
last_login DATETIME,
login_count INT
);
CREATE TABLE users_profile ( -- 资料表,很少访问
user_id INT PRIMARY KEY,
bio TEXT,
preferences JSON
);
查询模式:
sql
-- 登录验证(只访问核心表)
SELECT id, username, password FROM users_core WHERE username = ?;
-- 更新最后登录时间(只更新活动表)
UPDATE users_activity SET last_login = NOW() WHERE user_id = ?;
-- 查看用户资料(需要JOIN)
SELECT u.username, p.bio
FROM users_core u
JOIN users_profile p ON u.id = p.user_id
WHERE u.id = ?;
17. 拆分大的 DELETE 或 INSERT
详细解释:
大事务会长时间锁表,阻塞其他操作。
具体示例:
java
import java.sql.*;
public class BatchDeleteExample {
public void batchDeleteOldLogs() {
Connection conn = null;
PreparedStatement stmt = null;
try {
// 1. 创建连接
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test",
"username",
"password"
);
// 2. 关闭自动提交,可选(如果需要事务)
conn.setAutoCommit(false);
// 3. 准备删除语句
String sql = "DELETE FROM logs WHERE log_date < ? LIMIT 1000";
stmt = conn.prepareStatement(sql);
stmt.setString(1, "2023-01-01");
int totalDeleted = 0;
int affectedRows;
// 4. 循环分批删除
do {
// 执行删除
affectedRows = stmt.executeUpdate();
totalDeleted += affectedRows;
// 提交当前批次
conn.commit();
System.out.println("删除 " + affectedRows + " 条记录,总计: " + totalDeleted);
// 如果删除了数据,休息一会儿
if (affectedRows > 0) {
try {
Thread.sleep(100); // 休息 100ms
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
} while (affectedRows > 0);
System.out.println("删除完成,总共删除 " + totalDeleted + " 条记录");
} catch (SQLException e) {
e.printStackTrace();
// 回滚事务
try {
if (conn != null) conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// 5. 关闭资源
try {
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
批量插入优化:
sql
-- 大量插入也建议分批
INSERT INTO audit_log (user_id, action, timestamp) VALUES
(1, 'login', NOW()),
(2, 'logout', NOW()),
-- ... 每次插入 100-1000条
(100, 'view', NOW());
18. 越小的列越快
详细解释:
小数据类型占用更少存储空间,处理速度更快。
具体示例:
java
-- 过度设计:使用过大的数据类型
CREATE TABLE config (
id BIGINT PRIMARY KEY, -- 实际只有100条记录
value VARCHAR(1000), -- 实际平均长度20字符
created_at DATETIME, -- 不需要时间精度
flags BIGINT -- 实际只用了8个标志位
);
-- 优化后:使用合适的最小类型
CREATE TABLE config (
id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, -- 0-65535
value VARCHAR(50) NOT NULL DEFAULT '',
created_at DATE, -- 只需要日期
flags TINYINT UNSIGNED -- 0-255,足够存储8个标志位
);
存储空间计算:
sql
-- 原始设计:8 + 1000 + 8 + 8 = 1024字节/行
-- 优化后:2 + 50 + 3 + 1 = 56字节/行
-- 节省 95% 存储空间
19. 选择正确的存储引擎
详细解释:
不同存储引擎适合不同场景。
MyISAM vs InnoDB 对比:
| 特性 | MyISAM | InnoDB |
|---|---|---|
| 事务 | 不支持 | 支持 |
| 行级锁 | 表级锁 | 行级锁 |
| 外键 | 不支持 | 支持 |
| 崩溃恢复 | 较差 | 优秀 |
| COUNT(*) | 极快 | 需要扫描 |
| 全文索引 | 支持 | MySQL 5.6+ 支持 |
具体示例:
sql
-- 读多写少的日志表(适合 MyISAM)
CREATE TABLE access_log (
id INT PRIMARY KEY,
ip VARCHAR(15),
access_time DATETIME
) ENGINE=MyISAM;
-- 需要事务的用户表(必须 InnoDB)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE,
balance DECIMAL(10,2)
) ENGINE=InnoDB;
-- 会话表(适合 MEMORY)
CREATE TABLE sessions (
session_id VARCHAR(128) PRIMARY KEY,
user_id INT,
data TEXT,
expires DATETIME
) ENGINE=MEMORY;
20. 使用 ORM 的注意事项
详细解释:
ORM 提供便利但可能产生性能问题。
N+1 查询问题示例:
java
import java.sql.*;
import java.util.*;
public class NPlusOneProblemExample {
// 错误的做法:N+1 查询
public List<User> getUsersWithPostsNPlusOne() {
List<User> users = new ArrayList<>();
// 第一次查询:获取所有用户
String userSql = "SELECT id, username, email FROM users";
try (Connection conn = getConnection();
PreparedStatement userStmt = conn.prepareStatement(userSql);
ResultSet userRs = userStmt.executeQuery()) {
while (userRs.next()) {
User user = new User();
user.setId(userRs.getLong("id"));
user.setUsername(userRs.getString("username"));
user.setEmail(userRs.getString("email"));
// 为每个用户单独查询帖子(N+1 问题!)
List<Post> posts = getPostsByUserId(user.getId());
user.setPosts(posts);
users.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
}
return users;
}
// 单独查询用户帖子的方法(导致 N+1 问题)
private List<Post> getPostsByUserId(Long userId) {
List<Post> posts = new ArrayList<>();
String sql = "SELECT id, title, content, user_id FROM posts WHERE user_id = ?";
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setLong(1, userId);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Post post = new Post();
post.setId(rs.getLong("id"));
post.setTitle(rs.getString("title"));
post.setContent(rs.getString("content"));
post.setUserId(rs.getLong("user_id"));
posts.add(post);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return posts;
}
private Connection getConnection() throws SQLException {
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "username", "password");
}
}
ORM 优化技巧:
java
import java.sql.*;
import java.util.*;
public class EagerLoadingExample {
// 正确的做法:使用 JOIN 预加载
public List<User> getUsersWithPostsEagerLoading() {
List<User> users = new ArrayList<>();
Map<Long, User> userMap = new HashMap<>();
String sql = "SELECT u.id as user_id, u.username, u.email, " +
"p.id as post_id, p.title, p.content, p.user_id " +
"FROM users u " +
"LEFT JOIN posts p ON u.id = p.user_id " +
"ORDER BY u.id";
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Long userId = rs.getLong("user_id");
// 如果用户还不存在,创建用户对象
User user = userMap.get(userId);
if (user == null) {
user = new User();
user.setId(userId);
user.setUsername(rs.getString("username"));
user.setEmail(rs.getString("email"));
user.setPosts(new ArrayList<>());
userMap.put(userId, user);
users.add(user);
}
// 处理帖子数据(可能为 null,因为 LEFT JOIN)
Long postId = rs.getLong("post_id");
if (!rs.wasNull()) { // 检查帖子是否存在
Post post = new Post();
post.setId(postId);
post.setTitle(rs.getString("title"));
post.setContent(rs.getString("content"));
post.setUserId(userId);
user.getPosts().add(post);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return users;
}
}
21. 小心永久链接
详细解释:
持久连接可以减少连接建立开销,但可能造成连接堆积。
问题示例:
java
import java.sql.*;
public class BadConnectionExample {
private static Connection connection; // 静态持久连接
// 错误的做法:使用静态持久连接
public static Connection getConnection() throws SQLException {
if (connection == null || connection.isClosed()) {
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/database",
"user",
"password"
);
}
return connection;
}
// 使用示例 - 存在问题!
public void queryUsers() {
try {
Connection conn = getConnection(); // 获取持久连接
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// 处理结果...
while (rs.next()) {
System.out.println(rs.getString("username"));
}
// 注意:这里没有关闭连接!
// rs.close();
// stmt.close();
// 连接保持打开状态
} catch (SQLException e) {
e.printStackTrace();
}
}
// 另一个方法也使用同一个连接
public void updateUser(int userId) {
try {
Connection conn = getConnection(); // 同一个持久连接
PreparedStatement stmt = conn.prepareStatement(
"UPDATE users SET last_login = NOW() WHERE id = ?");
stmt.setInt(1, userId);
stmt.executeUpdate();
// 同样不关闭语句和连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
java
public class ConnectionProblems {
public static void main(String[] args) {
BadConnectionExample example = new BadConnectionExample();
// 在多线程环境下会产生严重问题
for (int i = 0; i < 10; i++) {
new Thread(() -> {
example.queryUsers();
example.updateUser(1);
}).start();
}
// 问题1: 连接状态混乱
// 一个线程设置的事务隔离级别会影响其他线程
// 一个线程开启事务会影响其他线程
// 问题2: 连接耗尽
// 每个应用实例保持一个连接,高并发时不够用
// 问题3: 资源泄漏
// 连接永远不会关闭,直到应用重启
}
}
现代解决方案:
使用 HikariCP
java
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import java.sql.*;
public class ConnectionPoolExample {
private static DataSource dataSource;
static {
// 初始化连接池
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/database");
config.setUsername("user");
config.setPassword("password");
// 连接池配置
config.setMaximumPoolSize(20); // 最大连接数
config.setMinimumIdle(5); // 最小空闲连接
config.setConnectionTimeout(30000); // 连接超时时间(ms)
config.setIdleTimeout(600000); // 空闲连接超时时间(ms)
config.setMaxLifetime(1800000); // 连接最大生命周期(ms)
config.setLeakDetectionThreshold(60000); // 泄漏检测阈值(ms)
// MySQL 特定配置
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
dataSource = new HikariDataSource(config);
}
// 正确的做法:从连接池获取连接,使用后关闭
public void queryUsers() {
// 使用 try-with-resources 自动关闭连接
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
while (rs.next()) {
System.out.println(rs.getString("username"));
}
} catch (SQLException e) {
e.printStackTrace();
}
// 连接自动返回到连接池,不会真正关闭
}
public void updateUser(int userId) {
String sql = "UPDATE users SET last_login = NOW() WHERE id = ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, userId);
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
总结表格
| 优化类别 | 关键实践 | 预期收益 |
|---|---|---|
| 查询优化 | EXPLAIN、LIMIT 1、避免 SELECT * | 20-50% 查询性能提升 |
| 索引优化 | 为搜索字段建索引、JOIN字段类型一致 | 10-100倍查询加速 |
| 表结构设计 | 合适的数据类型、垂直分割、NOT NULL | 30-80% 存储空间节省 |
| 操作优化 | 分批处理大操作、使用预处理语句 | 避免服务中断,提升并发 |
| 架构选择 | 正确存储引擎、谨慎使用ORM和持久连接 | 系统稳定性和扩展性 |
这些优化实践需要根据具体业务场景和数据特征进行选择和调整。建议先通过 EXPLAIN 和慢查询日志识别瓶颈,再有针对性地实施优化。