结论是:应显式控制事务边界并强制连接池初始化设autocommit=1;因autocommit=0后单条SELECT即隐式开启长事务,且不提交则一直持有锁,易被驱动默认覆盖或连接池复用导致残留。
直接结论:不要靠关 autocommit 来“防长事务”,而要靠显式控制事务边界 + 连接池初始化强制设 autocommit = 1。
autocommit = 0 后,哪怕只执行一条 SELECT,也会隐式开启一个事务(InnoDB 中所有读都属于某个事务上下文),且不会自动结束。连接不关闭、不 COMMIT 或 ROLLBACK,这个事务就一直挂着——SELECT @@in_transaction; 返回 1,information_schema.innodb_trx 里就能查到它。
常见诱因包括:
set autocommit = 0,你根本没写这句,但它已经生效了connection-init-sql,HikariCP 连上来就是 autocommit = 0COMMIT,循环插入卡在中间,事务持续数分钟别信 SET GLOBAL autocommit = 1 —— 它只影响后续新建连接的初始值,但 JDBC 驱动仍可能在连接建立后立刻覆盖成 0。真正起作用的是连接池的初始化 SQL:
spring.datasource.hikari.connection-init-sql=SET autocommit = 1
connectionInitSqls=["SET autocommit = 1"]
--init-command="SET autocommit = 1"
验证是否生效:连上后立刻执行 SELECT @@autocommit;,必须是 1;再执行一条 UPDATE,立刻能被其他会话查到,且 SELECT @@in_transaction; 为 0。
无脑用 START TRANSACTION。它语义清晰、行为确定,且不会污染会话状态。
SET autocommit = 0 的问题在于:
ALTER TABLE)、LOCK TABLES、TRUNCATE TABLE 会触发隐式 COMMIT,导致你以为还在事务里,其实早已提交ROLLBACK,连接复用时残留事务状态,下个请求一执行 SELECT 就卡住正确姿势:START TRANSACTION → 执行业务 SQL → COMMIT 或 ROLLBACK。批量场景下,每 100~500 行后 COMMIT 并重新 START TRANSACTION,避免单事务过久。
别等锁爆发,日常监控看两件事:
SELECT trx_id, trx_started, trx_state, trx_mysql_thread_id FROM information_schema.innodb_trx WHERE TIME_TO_SEC(NOW() - trx_started) > 60;
SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist WHERE time > 60 AND command != 'Sleep';
特别注意那些 command = 'Sleep' 但 time 很大的连接——大概率是 autocommit = 0 后没 COMMIT,连接空闲但事务还开着。这类连接在高并发下极易堆积,拖垮整个实例。
最常被忽略的一点:autocommit 状态不是全局开关,而是每个连接独立维护的。同一应用里,不同线程、不同数据源、甚至同一线程内多次获取连接,都可能拿到不同 autocommit 值的连接。所以不能靠“我之前设过”来假设安全,每次关键操作前,宁可用 SELECT @@autocommit; 和 SELECT @@in_transaction; 快速确认。