在phpEnv这类一站式集成开发环境中,MySQL通常以便捷部署为目标,很多底层监控参数并没有开到生产级。当业务代码里出现未及时提交的事务,或者一条事务里夹杂了远程接口调用,事务持有锁的时间会被大幅拉长。这种长事务会占用undo日志、阻塞后续读写,是数据库稳定性最容易忽视的隐患。要在phpEnv里把长事务记录下来做监控,核心思路是利用MySQL自身的能力,而不是额外装一套复杂系统。

一、为什么长事务影响phpEnv数据库稳定性
长事务指的是从begin到commit或rollback之间耗时明显超过正常业务上限的事务。在phpEnv自带的MySQL中,如果某个事务开启了却因为代码逻辑忘了提交,连接池里的连接就会被长期占用。其他请求在访问相同行或相同索引区间时,不得不等待锁释放,接口响应时间随之陡增。
更隐蔽的问题是,长事务会让MySQL的MVCC多版本链变长。InnoDB需要保留旧版本数据以便长事务读取一致性快照,导致undo表空间无法 purge,磁盘占用悄悄上涨。很多开发者在phpEnv里遇到过“数据库越跑越慢”,翻半天慢查询日志却一无所获,其实就是长事务在作怪,而慢查询日志默认只记单条SQL,不记事务跨度。
二、用performance_schema开启长事务记录
MySQL自带的performance_schema提供了事务事件记录,比general log轻量,也比binlog更适合做监控。在phpEnv中,先确认performance_schema是开启状态。可以通过SQL查看,如果没开,需要在phpEnv的MySQL配置文件里加上performance_schema=ON然后重启服务。
开启后还要确保事务相关的采集器被启用。setup_consumers里transaction类型的消费者需要为YES,setup_instruments中transaction相关的工具也要启用。可以用如下语句批量打开,避免手动改表出错。
-- 开启事务相关的instrument UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'transaction%'; -- 开启事务相关的consumer UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%transactions%';
配置完成后,所有事务的起止时间、线程ID都会进入events_transactions_current和events_transactions_history_long。后者保留最近一段时间内已结束事务的样本,是我们提取长事务的主要来源。需要注意history_long有长度上限,高并发下可能覆盖较快,但对定位明显异常已经足够。
三、提取长事务并写入日志文件
我们可以定义一个阈值,比如超过5秒的事务算长事务。通过定时任务每秒或每五秒去查一次history_long,把耗时超阈值的记录下来。下面这段存储过程思路可供参考,它在phpEnv的MySQL里直接把结果插到一张监控表中。
-- 创建长事务记录表
CREATE TABLE IF NOT EXISTS slow_tx_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
thread_id BIGINT,
duration_ms BIGINT,
state VARCHAR(64),
tx_start TIMESTAMP,
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入长事务
INSERT INTO slow_tx_log (thread_id, duration_ms, state, tx_start)
SELECT THREAD_ID,
TIMER_END - TIMER_START,
STATE,
TIMESTAMP(START_EVENT_ID)
FROM performance_schema.events_transactions_history_long
WHERE (TIMER_END - TIMER_START) > 5000000000
AND STATE = 'COMMITTED';
上面的TIMER单位是皮秒,所以5秒对应5000000000。如果希望从PHP层面落地成文本日志,可以在phpEnv里写个计划任务脚本,用PDO查出来然后file_put_contents追加到日志文件。这样运维同学不看数据库也能通过日志系统收集。
<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=mysql', 'root', 'phpenv_pass');
$sql = "SELECT THREAD_ID, (TIMER_END-TIMER_START) AS dur, STATE
FROM performance_schema.events_transactions_history_long
WHERE (TIMER_END-TIMER_START) > 5000000000";
$rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
if ($rows) {
$log = date('Y-m-d H:i:s') . " 长事务:n" . print_r($rows, true) . "n";
file_put_contents('/var/log/phpEnv_long_tx.log', $log, FILE_APPEND);
}
?>
四、与phpEnv监控面板结合的轻量方案
phpEnv自带简单的服务状态页,但不暴露事务维度。我们可以把上面提到的slow_tx_log表用Grafana或自建HTML页轮询展示。对于不想引入额外组件的用户,写个最小接口返回最近十条长事务即可,前端用表格呈现线程ID和耗时。
| 线程ID | 耗时(毫秒) | 状态 | 记录时间 |
|---|---|---|---|
| 48 | 6320 | COMMITTED | 2024-03-11 10:22:01 |
| 51 | 8105 | ROLLED BACK | 2024-03-11 10:25:44 |
这种方案的优点是零侵入业务代码,只在MySQL侧做采集;缺点是无法精确到事务内执行了哪些SQL,如果需要更细粒度,要把events_statements_history_long按线程ID关联出来。但在phpEnv本地调试和中小项目生产环境,先解决“有没有长事务”比“长事务里有什么”更紧迫。
五、常见误区与参数调优
有人以为打开slow_query_log就能抓长事务,其实慢日志只针对单条语句超过long_query_time的情况。一个事务由十句各耗时0.4秒的SQL组成,总时长4秒,但慢日志不会记。所以必须靠performance_schema的事务事件,两者互补。
另一个误区是history_long大小不用调。phpEnv默认performance_schema的history_long尺寸较小,高并发时长事务可能被冲掉。可通过调整performance_schema_events_transactions_history_long_size参数增大容量,但会占用更多内存,本地开发机建议设为10000左右,生产按内存余量评估。
最后提醒,监控脚本自身也要加超时和异常捕获,别让记录长事务的定时任务变成新的长事务。把查询包在READ ONLY事务里,并设置max_execution_time,避免雪崩。