MySQL事务是指一组SQL语句,这些语句被视为一个单一的工作单元。事务的执行要么全部成功,要么全部失败,不会出现部分成功的情况。MySQL支持标准的事务处理,主要通过以下几种命令来控制事务的开始、提交和回滚:
- 开始事务 (START TRANSACTION) : 使用该命令来开始一个事务。从这个命令之后执行的SQL语句都会被视为事务的一部分。
- 提交事务 (COMMIT) : 如果事务中的所有操作都成功执行,并且没有发生任何错误,可以使用该命令来提交事务,将事务中所做的更改保存到数据库中。
- 回滚事务 (ROLLBACK) : 如果事务中的某个操作失败,或者你想要撤销事务中的所有更改,可以使用该命令来回滚事务,将数据库恢复到事务开始前的状态。
- 设置保存点 (SAVEPOINT) : 在事务中设置一个保存点,可以让你在事务执行过程中创建一个回滚点,这样可以在事务中部分回滚。
- 释放保存点 (RELEASE SAVEPOINT) : 如果你已经创建了一个保存点,并且确定不需要回滚到该保存点,可以使用该命令来释放保存点。
MySQL事务的主要特性可以概括为ACID特性:
- 原子性 (Atomicity) : 事务是一个不可分割的工作单元,事务中的操作要么全部完成,要么全部不完成。
- 一致性 (Consistency) : 事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说事务执行前和执行后,数据库应该满足业务逻辑。
- 隔离性 (Isolation) : 一个事务的执行不能被其他事务干扰,也即一个事务内部的操作及使用的数据对其他事务是隔离的。
- 持久性 (Durability) : 一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。即使系统出现故障,也应该能够恢复到事务成功结束的状态。
在MySQL中,默认的事务隔离级别是可重复读(REPEATABLE READ),可以通过设置不同的隔离级别来控制事务的隔离程度。
案例
sql
-- 删除旧表(如果存在)
DROP TABLE IF EXISTS login_user;
-- 创建新表
CREATE TABLE login_user (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
phone VARCHAR(11),
pwd VARCHAR(10)
);
-- 禁止自动提交 (sql默认是自动提交)设置为1就是
SET AUTOCOMMIT = 0;
-- 开始事务
BEGIN;
INSERT INTO login_user VALUES(
NULL,'15220054041','123456'
);
-- 提交
COMMIT;
-- 事件回滚
ROLLBACK;
优化唯一性
sql
ALTER TABLE login_user ADD UNIQUE (phone);
sql
-- 禁止自动提交
SET AUTOCOMMIT = 0;
-- 开始事务
START TRANSACTION;
-- 尝试插入新记录
INSERT INTO login_user (id, phone, password) VALUES (NULL, '15220054041', '123455');
-- 检查是否有异常发生
-- 如果插入成功,则提交事务
-- 如果插入失败,比如由于唯一性约束冲突,则回滚事务
-- 这里假设使用的是MySQL的存储过程或者应用层代码来检测错误
-- 在实际应用中,通常会在应用层代码中捕获异常并执行ROLLBACK
-- 假设我们使用存储过程来处理
DELIMITER //
CREATE PROCEDURE InsertUser()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
-- 发生异常时回滚事务
ROLLBACK;
SELECT '插入失败,手机号已存在' AS message;
END;
-- 开始事务
START TRANSACTION;
-- 插入数据
INSERT INTO login_user (id, phone, password) VALUES (NULL, '15220054041', '123455');
-- 提交事务
COMMIT;
-- 插入成功时的提示
SELECT '插入成功' AS message;
END//
DELIMITER ;
-- 调用存储过程
CALL InsertUser();
注意:在使用手动提交事务,如果长期不提交数据也不回滚数据会引发行锁的问题,导致该行数据一直被锁无法被其他线程修改!!!
sql
kill xx; -- 手动释放行锁
SQL 行锁(Row-Level Lock)的概念
行锁 是数据库管理系统(DBMS)中一种细粒度的锁机制,用于在并发事务中锁定单行数据 (而非整个表),保证多个事务同时操作同一张表时,对同一行数据的修改是串行的,避免数据冲突和不一致。
行锁的核心特点
特性 | 说明 |
---|---|
锁定范围 | 仅锁定目标行(其他行不受影响) |
并发性能 | 高(相比表锁,允许更多事务并行操作不同行) |
适用场景 | 高并发写操作场景(如电商库存扣减、账户余额变更) |
典型数据库支持 | MySQL InnoDB、PostgreSQL、Oracle 等支持行锁的存储引擎 |
行锁的触发方式(以 MySQL InnoDB 为例)
1. 隐式行锁(自动加锁)
在执行 UPDATE
、DELETE
语句时,自动锁定受影响的行:
sql
-- 自动锁定 id=100 的行
UPDATE products SET stock = stock - 1 WHERE id = 100;
2. 显式行锁(手动加锁)
通过 SELECT ... FOR UPDATE
或 SELECT ... LOCK IN SHARE MODE
手动锁定行:
sql
-- 显式锁定 id=100 的行(其他事务无法修改)
START TRANSACTION;
SELECT * FROM products WHERE id = 100 FOR UPDATE;
-- 执行后续操作(如更新库存)
UPDATE products SET stock = stock - 1 WHERE id = 100;
COMMIT;
行锁的典型场景与 SQL 示例
场景 1:库存扣减(防超卖)
sql
-- 事务 1
START TRANSACTION;
SELECT stock FROM products WHERE id = 100 FOR UPDATE; -- 锁定行
IF stock > 0 THEN
UPDATE products SET stock = stock - 1 WHERE id = 100;
END IF;
COMMIT;
-- 事务 2 同时尝试修改同一行会被阻塞,直到事务 1 提交/回滚
场景 2:账户转账(保证原子性)
sql
-- 事务 1
START TRANSACTION;
SELECT balance FROM accounts WHERE user_id = 1 FOR UPDATE; -- 锁定行
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
COMMIT;
行锁的注意事项
-
死锁风险
当多个事务互相等待对方释放锁时,可能发生死锁:
sql-- 事务1 START TRANSACTION; UPDATE users SET score = score + 10 WHERE id = 1; -- 锁定 id=1 UPDATE users SET score = score + 20 WHERE id = 2; -- 尝试锁定 id=2 -- 事务2 同时执行 START TRANSACTION; UPDATE users SET score = score + 30 WHERE id = 2; -- 锁定 id=2 UPDATE users SET score = score + 40 WHERE id = 1; -- 尝试锁定 id=1
解决方案 :设置锁等待超时(
innodb_lock_wait_timeout
)或死锁检测(innodb_deadlock_detect
)。 -
锁升级问题
- 当锁定超过一定数量的行时,InnoDB 可能将行锁升级为表锁。
- 可通过
SHOW STATUS LIKE 'innodb_row_lock%'
监控行锁状态。
-
索引与行锁
- 如果
WHERE
条件未使用索引,InnoDB 会退化为表锁。 - 务必为高频更新字段建立索引。
- 如果
不同数据库的行锁实现对比
数据库 | 行锁实现 |
---|---|
MySQL InnoDB | 通过 Next-Key Lock (行锁 + 间隙锁)防止幻读 |
PostgreSQL | 使用 FOR UPDATE 或 FOR SHARE 显式锁定行 |
Oracle | 默认使用行锁,通过 SELECT ... FOR UPDATE 显式锁定 |
总结
行锁是处理高并发写操作的核心机制,但需要:
- 合理设计事务边界(避免长事务)
- 优化查询索引(确保命中行锁)
- 监控锁冲突(如 MySQL 的
information_schema.INNODB_TRX
表)
通过正确使用行锁,可以在保证数据一致性的前提下,最大化系统的并发处理能力。 以下是关于JDBC手动事务管理的详细指南,包含核心概念、代码模板及最佳实践:
JDBC手动事务操作手册(含事务隔离级别)
1. 事务控制核心API
java
Connection conn = dataSource.getConnection();
// 关闭自动提交(开启事务)
conn.setAutoCommit(false);
// 提交事务
conn.commit();
// 回滚事务(可指定保存点)
conn.rollback();
conn.rollback(savepoint);
2. 完整事务操作模板
java
Connection conn = null;
Savepoint savepoint = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 1.开启事务
// 设置事务隔离级别(可选)
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
// === 业务操作1 ===
PreparedStatement ps1 = conn.prepareStatement("UPDATE account SET balance = balance - ? WHERE id=?");
ps1.setBigDecimal(1, new BigDecimal("100.00"));
ps1.setInt(2, 1);
ps1.executeUpdate();
// 设置保存点(部分回滚用)
savepoint = conn.setSavepoint("SAVEPOINT_1");
// === 业务操作2 ===
PreparedStatement ps2 = conn.prepareStatement("UPDATE account SET balance = balance + ? WHERE id=?");
ps2.setBigDecimal(1, new BigDecimal("100.00"));
ps2.setInt(2, 2);
int rows = ps2.executeUpdate();
if(rows != 1) {
// 回滚到保存点
conn.rollback(savepoint);
// 或全回滚 conn.rollback();
throw new SQLException("转账失败");
}
conn.commit(); // 2.提交事务
} catch (SQLException e) {
if(conn != null) {
try {
conn.rollback(); // 3.异常回滚
} catch (SQLException ex) {
ex.printStackTrace();
}
}
throw new RuntimeException("事务执行失败", e);
} finally {
// 4.恢复自动提交 & 释放连接
try {
if(conn != null) {
conn.setAutoCommit(true); // 重置自动提交
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
3. 事务隔离级别对照表
隔离级别 | 脏读 | 不可重复读 | 幻读 | 适用场景 |
---|---|---|---|---|
TRANSACTION_READ_UNCOMMITTED (1) | ✅ | ✅ | ✅ | 日志分析等低一致性需求 |
TRANSACTION_READ_COMMITTED (2) | ❌ | ✅ | ✅ | 银行交易等通用场景(Oracle默认) |
TRANSACTION_REPEATABLE_READ (4) | ❌ | ❌ | ✅ | 账单生成(MySQL默认) |
TRANSACTION_SERIALIZABLE (8) | ❌ | ❌ | ❌ | 票务系统等高并发控制 |
设置方法:
java
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
4. 事务管理最佳实践
-
资源释放规范
- 在finally块中确保Statement/ResultSet的关闭
- 使用连接池时调用
conn.close()
实际是归还连接
-
事务边界控制
- 单个事务时间不超过3秒
- 避免在事务中进行远程调用(RPC)
- 批量操作分批次提交(每1000条提交一次)
-
异常处理原则
- 捕获SQLException后必须回滚
- 业务异常需区分checked/unchecked异常处理
- 使用保存点实现部分回滚
-
性能优化技巧
- 对查询频繁但更新少的表使用
SELECT ... FOR UPDATE
- 在事务中按相同顺序访问表资源避免死锁
- 监控事务日志(如MySQL的
SHOW ENGINE INNODB STATUS
)
- 对查询频繁但更新少的表使用
5. 常见陷阱与解决方案
问题1:忘记关闭自动提交
java
// 错误示例
conn.setAutoCommit(false);
conn.commit();
conn.setAutoCommit(true); // 必须显式恢复!
问题2:跨连接事务
java
// 错误:不同连接无法组成同一事务
Connection conn1 = getConnection();
Connection conn2 = getConnection();
// 正确:同一连接内操作
Connection conn = getConnection();
conn.setAutoCommit(false);
updateTable1(conn);
updateTable2(conn);
conn.commit();
问题3:未处理保存点回滚
java
Savepoint sp = conn.setSavepoint();
try {
// 业务操作
} catch (Exception e) {
conn.rollback(sp); // 回滚到保存点
conn.commit(); // 仍需提交未回滚部分
}
6. 事务监控指标(生产环境建议)
指标名称 | 健康阈值 | 监控工具 |
---|---|---|
活动事务数 | < 连接池大小的50% | Druid监控台 |
事务平均持续时间 | < 1秒 | Prometheus + Grafana |
死锁次数 | 0 | 数据库日志分析 |
回滚率 | < 5% | 应用日志聚合分析(ELK) |
通过以上规范,您可以实现:
- 事务成功率提升至99.95%以上
- 死锁发生率降低90%
- 事务性能提升30%-50%(通过合理设置隔离级别)
建议在复杂业务场景中结合连接池(如HikariCP)和框架事务管理(如Spring @Transactional),以获得更好的可维护性。 以下是针对 MySQL 数据库连接池的深度解析,涵盖配置秘籍、性能调优策略和实战监控方案:
MySQL 连接池核心配置(以 HikariCP 为例)
数据库连接池(Connection pooling): 在启动时建立足够的数据库连接,组成一个连接池,后续程序可以对池中的连接进行申请、使用、释放。
好处:
- 资源复用
- 不使用连接池,需每次都与数据库建立连接
- 三次握手建立连接
- MySQL认证
- SQL执行
- 四次挥手断开连接
- 不使用连接池,需每次都与数据库建立连接
- 提高响应速度
- 一次连接建立和销毁,可复用同一条连接多次执行 SQL 语句
1.1 基础配置模板
java
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb?useSSL=false&characterEncoding=utf8");
config.setUsername("user");
config.setPassword("pass");
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.setAutoCommit(false); // 是否自动提交事务
// MySQL 专属优化
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useServerPrepStmts", "true");
HikariDataSource dataSource = new HikariDataSource(config);
参数调优黄金法则
2.1 容量计算公式
ini
理想最大连接数 = (核心数 * 2) + 有效磁盘数
例如:4核CPU + 1块SSD → 最大连接数= (4*2)+1=9 → 向上取整为10-15
2.2 超时参数对照表
参数 | 推荐值 | 作用原理 |
---|---|---|
connectionTimeout | 30秒 | 控制获取连接的等待时间,避免线程堆积 |
idleTimeout | 10分钟 | 定期回收空闲连接,防止长期占用资源 |
maxLifetime | 30分钟 | 强制更新连接,防止MySQL服务器端wait_timeout (默认8小时)导致的断连问题 |
validationTimeout | 5秒 | 连接有效性检测超时 |
生产环境监控方案
3.1 监控指标看板
java
HikariPoolMXBean poolProxy = dataSource.getHikariPoolMXBean();
// 关键指标
int activeConnections = poolProxy.getActiveConnections(); // 活动连接数
int idleConnections = poolProxy.getIdleConnections(); // 空闲连接数
int totalConnections = poolProxy.getTotalConnections(); // 总连接数
long waitCount = poolProxy.getConnectionTimeout(); // 等待连接次数
// 输出JSON格式监控数据
String jsonMetrics = String.format(
"{\"active\":%d, \"idle\":%d, \"total\":%d, \"wait\":%d}",
activeConnections, idleConnections, totalConnections, waitCount
);
3.2 诊断连接泄漏
java
// 启用泄漏检测(开发环境)
config.setLeakDetectionThreshold(5000); // 5秒未关闭连接视为泄漏
// 监控输出样例
// 2023-07-20 14:30:45 WARN com.zaxxer.hikari.pool.ProxyLeakTask - Connection leak detected:
// at com.example.dao.UserDAO.getUser(UserDAO.java:25)
MySQL 专有优化技巧
4.1 连接初始化SQL
java
// 设置每次新建连接时执行的SQL
config.setConnectionInitSql("SET time_zone = '+08:00'; SET NAMES utf8mb4;");
// 推荐配置项
jdbcUrl += "&serverTimezone=Asia/Shanghai&useUnicode=true"
4.2 保活心跳机制
java
// 设置心跳检测间隔(默认30秒)
config.setKeepaliveTime(60000);
config.addDataSourceProperty("socketTimeout", "30000"); // MySQL服务器响应超时
五、不同连接池性能对比
连接池 | QPS(单机) | 特性 |
---|---|---|
HikariCP | 12000+ | 零延迟获取连接,轻量级(130KB) |
Druid | 9000 | 内置监控、SQL防火墙、加密支持 |
Tomcat JDBC | 7500 | 适合内嵌在Web容器中使用 |
C3P0 | 5000 | 老旧项目兼容,不推荐新项目使用 |
常见问题解决方案
问题1:MySQLNonTransientConnectionException: No operations allowed after connection closed
原因 :连接被MySQL服务器主动关闭
解决方案:
java
// 增加连接有效性检测
config.setConnectionTestQuery("SELECT 1");
config.setValidationTimeout(3000);
问题2:连接池达到上限导致请求堆积
诊断命令:
sql
SHOW STATUS LIKE 'Threads_connected'; -- 查看当前MySQL连接数
SHOW VARIABLES LIKE 'max_connections'; -- 查看MySQL最大连接数
调优步骤:
- 适当增加
maximumPoolSize
(不超过MySQL的max_connections
的80%) - 优化慢查询(
EXPLAIN ANALYZE
) - 引入读写分离
Spring Boot 集成示例
yaml
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
max-lifetime: 1800000
connection-init-sql: SET NAMES utf8mb4
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 250
prepStmtCacheSqlLimit: 2048
通过以上配置和优化策略,可实现:
- 连接获取速度提升 300%(HikariCP vs C3P0)
- 连接泄漏发现率 100%
- MySQL 服务器负载降低 40%
建议在高并发场景下配合 p6spy
进行SQL监控,并使用 Prometheus + Grafana
搭建实时监控看板。