0
3
3
3
博客/.../

TiDB 8.5 接口平台测试环境下的消息积压排查记录

 拍脑袋小助手  发表于  2026-09-18
原创测试

上一篇的日常巡检,主要是做接口账号的连接、长事务和慢 SQL ,实际业务中经常出现的问题还是医院接口平台侧:LIS 的待发送消息越来越多,数据库这一侧应该怎么查。

HIS 往 LIS 发送检验申请,往 PACS 发送检查申请,EMR、集成平台和第三方系统之间也会不断产生接口数据。业务系统把消息写进表,接口程序读取、发送,再更新处理状态。待发送数量增加时,先看消息停在哪个状态,再查连接和 SQL。下游接收失败、消费线程卡住、重试增多,都可能让表里的消息堆起来,不能只凭数量增加就认定是慢查询。

一、准备接口表和检查账号

测试库沿用 hip_lab,接口表使用 hip_interface_message。建表前确认 hip_lab 已存在;应用账号为 hip_app,检查时使用具备相应权限的 DBA 账号。查看其他账号的完整会话和事务信息、查询锁等待和死锁表,需要 PROCESS 权限。

CREATE TABLE hip_lab.hip_interface_message (
    id              BIGINT NOT NULL AUTO_INCREMENT,
    message_no      VARCHAR(40) NOT NULL COMMENT '接口消息唯一号',
    visit_no        VARCHAR(32) DEFAULT NULL COMMENT '测试就诊号',
    source_system   VARCHAR(16) NOT NULL COMMENT '来源系统',
    target_system   VARCHAR(16) NOT NULL COMMENT '目标系统',
    message_type    VARCHAR(24) NOT NULL COMMENT '消息类型',
    process_status  TINYINT NOT NULL DEFAULT 0
                    COMMENT '0待发送 1处理中 2成功 9失败',
    retry_count     SMALLINT NOT NULL DEFAULT 0 COMMENT '重试次数',
    created_at      DATETIME(3) NOT NULL COMMENT '消息产生时间',
    next_retry_at   DATETIME(3) DEFAULT NULL COMMENT '下次重试时间',
    processed_at    DATETIME(3) DEFAULT NULL COMMENT '处理完成时间',
    payload         MEDIUMTEXT DEFAULT NULL COMMENT '接口报文',
    PRIMARY KEY (id),
    UNIQUE KEY uk_message_no (message_no),
    KEY idx_visit_no (visit_no),
    KEY idx_poll_target_status_time (
        target_system,
        process_status,
        created_at,
        id
    )
);

这里的状态值由接口程序约定,主要看目标系统、处理状态和消息时间,再结合重试次数判断处理进度。7.5 系列已经讨论过轮询 SQL 和组合索引,这篇保留原有索引,从消息状态开始排查。

二、确认哪些消息没有处理完

收到“LIS 消息积压”的反馈后,先按目标系统和状态统计最近一天产生的消息。成功历史记录也会保留在接口表里,需要和待发送、处理中、失败的消息分开看。

SELECT
    target_system,
    process_status,
    COUNT(*) AS message_count,
    MIN(created_at) AS oldest_message,
    MAX(created_at) AS newest_message
FROM hip_lab.hip_interface_message
WHERE created_at >= NOW() - INTERVAL 1 DAY
GROUP BY
    target_system,
    process_status
ORDER BY
    target_system,
    process_status;

结果示例,只摘录待发送和失败的几行,时间显示到分钟:

target_system process_status message_count oldest_message
EMR 0 3 2026-09-17 22:57
LIS 0 426 2026-09-17 22:21
LIS 9 61 2026-09-17 22:24
PACS 0 1 2026-09-17 23:08

这组数据里,LIS 待发送消息较多,最早一条的产生时间也比 EMR、PACS 更早。我把 LIS 作为后面检查的重点,同时检查状态为 1(处理中)的消息;处理了多久,还要用应用日志里的领取时间核对,不能直接拿产生时间计算。单个目标系统异常可以帮助缩小范围,但还不能排除它使用的 SQL、事务或访问节点有问题。

这条 SQL 只覆盖最近一天产生的消息,更早的积压不会出现在结果里。下面查询全部待发送记录时,去掉产生时间限制,避免漏掉旧消息。

三、看最老的一条已经等了多久

只看待发送数量,还不知道消息有没有继续向前处理。把最早产生时间和等待分钟数一起查出来,并和前一次记录对照。

SELECT
    target_system,
    COUNT(*) AS pending_count,
    MIN(created_at) AS oldest_created_at,
    TIMESTAMPDIFF(
        MINUTE,
        MIN(created_at),
        NOW()
    ) AS oldest_wait_minutes
FROM hip_lab.hip_interface_message
WHERE process_status = 0
GROUP BY target_system
ORDER BY oldest_wait_minutes DESC;

延续上面的结果示例,LIS 的待发送数量较多,最早消息的产生时间也明显早于另外两个系统。等待了多少分钟,要以这次查询的时间计算;是否超出正常范围,还要对照接口约定的处理时限。

然后隔一段时间再查,比较待发送数量和最老消息时间。连续记录才能判断数量是否持续增加;最老消息时间一直不变时,还要查是否有消息长期滞留。接口表里保留几百万条成功记录,本身不能说明积压;这条查询也只统计状态 0,处理中和失败的消息还要分别检查。

四、比较十分钟内的新增量和成功完成量

消息可能一直在处理,只是完成速度没有跟上。把观察窗口固定为十分钟,分别按 created_at 和 processed_at 统计,完成量只计状态为 2 的成功消息。

SET @window_end = NOW(3);
SET @window_begin = @window_end - INTERVAL 10 MINUTE;

SELECT
    COUNT(
        CASE
            WHEN created_at >= @window_begin
             AND created_at < @window_end
            THEN 1
        END
    ) AS created_last_10m,
    COUNT(
        CASE
            WHEN process_status = 2
             AND processed_at >= @window_begin
             AND processed_at < @window_end
            THEN 1
        END
    ) AS succeeded_last_10m
FROM hip_lab.hip_interface_message
WHERE target_system = 'LIS';

新增消息和成功消息分别按自己的时间字段统计。十分钟前产生、在这十分钟内完成的消息,也要计入成功完成量,不能在外层再用 created_at 把它过滤掉。这项统计要求成功记录仍保留在表中,完成状态和时间没有被重置;失败消息即使填写了 processed_at,也不会被算成成功。

比较多个时间段时,记录各自的起止时间,使用相邻、不重叠的窗口。在没有清理消息或重置状态的情况下,新增量持续大于成功完成量,未成功完成的消息就会累积。这些消息可能处于 0、1 或 9,差值不能直接当作待发送数量的增量,原因还要结合失败记录和接口程序继续查。这里的汇总需要读取 LIS 消息记录,不能把它当成高频轮询语句反复执行。

五、把失败消息和重试记录单独查出来

前面的示例还有 61 条 LIS 失败消息。按目标系统和消息类型展开,看失败是否集中在某一类业务消息,再看重试次数。

SELECT
    target_system,
    message_type,
    COUNT(*) AS failed_count,
    MAX(retry_count) AS max_retry_count,
    MIN(created_at) AS oldest_message_created_at,
    MAX(next_retry_at) AS latest_next_retry_at
FROM hip_lab.hip_interface_message
WHERE process_status = 9
  AND created_at >= NOW() - INTERVAL 1 DAY
GROUP BY
    target_system,
    message_type
ORDER BY failed_count DESC;

oldest_message_created_at 是这批失败消息中最早的产生时间,不能当作首次失败时间;latest_next_retry_at 是最晚的计划重试时间,也不能当作最近一次实际重试的时间。当前表没有记录每次失败发生的时间,这部分要去应用日志里找。如果积压已经超过一天,失败消息的查询范围也要相应放宽。

按状态约定,失败记录和非零重试次数表明应用写入过失败、重试信息。要确认重试是否还在增加,对照同一 message_no 前后的 retry_count,不能只看一次 MAX(retry_count)。这些字段无法证明请求已经发到 LIS,也无法区分下游业务拒绝、网络超时和报文解析失败,需要继续核对接口日志。

六、检查接口账号的数据库连接

消息状态看完,查 hip_app 的连接。集群有多个 TiDB Server,使用 CLUSTER_PROCESSLIST 可以汇总各节点的会话,避免只看到当前连接节点的情况。

SELECT
    INSTANCE,
    USER,
    DB,
    COMMAND,
    COUNT(*) AS session_count
FROM information_schema.CLUSTER_PROCESSLIST
WHERE USER = 'hip_app'
GROUP BY
    INSTANCE,
    USER,
    DB,
    COMMAND
ORDER BY
    INSTANCE,
    COMMAND;

如果没有查到 hip_app 连接,先确认接口应用是否运行,查看连接池、网络和客户端报错。查询账号权限也要核对,不能把看不到其他用户的连接当成没有连接。连接仍在时,再查这些会话当前执行什么;连接数本身不能证明消费线程正常。

七、查看接口会话当前在执行什么

下面排除 Sleep 会话,按持续时间排序。主要看有没有长时间不结束的 SQL,以及多个会话是否都停在同一类更新语句上。

SELECT
    INSTANCE,
    ID,
    USER,
    HOST,
    DB,
    COMMAND,
    TIME,
    STATE,
    LEFT(INFO, 300) AS SQL_TEXT
FROM information_schema.CLUSTER_PROCESSLIST
WHERE USER = 'hip_app'
  AND COMMAND <> 'Sleep'
ORDER BY TIME DESC;

PROCESSLIST 反映查询时正在处理的请求。轮询 SQL 可能很快结束,查系统表时不一定正好碰上。当前没有非空闲会话,只能记录这次没有看到正在执行的语句,不能据此判断接口程序已经停止工作;近期是否持续访问,继续查 Statement Summary。

八、用 Statement Summary 查看近期访问

TiDB 默认开启 Statement Summary,按 SQL Digest、Plan Digest 等信息聚合语句,可以查看执行次数、使用的索引和最近出现时间。用 CLUSTER_STATEMENTS_SUMMARY 查询整个集群的当前汇总周期。

SELECT
    INSTANCE,
    DIGEST_TEXT,
    EXEC_COUNT,
    INDEX_NAMES,
    FIRST_SEEN,
    LAST_SEEN
FROM information_schema.CLUSTER_STATEMENTS_SUMMARY
WHERE SCHEMA_NAME = 'hip_lab'
  AND DIGEST_TEXT LIKE '%hip_interface_message%'
ORDER BY LAST_SEEN DESC
LIMIT 20;

这里按默认库 hip_lab 筛选。如果应用连接使用其他默认库,再通过全限定表名访问接口表,就要调整筛选条件,不能直接用空结果判断没有访问。查询也没有限定执行账号,同类语句可能来自其他账号,归属还要结合连接信息和应用日志确认。

下面是轮询和状态更新两类语句的形态示例。? 表示参数占位,省略了归一化文本中的引号等格式,不能直接复制执行。

SELECT id, message_no, visit_no, message_type, created_at
FROM hip_interface_message
WHERE target_system = ?
  AND process_status = ?
ORDER BY created_at, id
LIMIT ?;
UPDATE hip_interface_message
SET process_status = ?,
    processed_at = ?
WHERE id = ?;

对照同一汇总周期内的记录,看 EXEC_COUNT 是否增加、LAST_SEEN 是否向后推进。这能确认同类 SQL 仍有执行记录,但不能直接证明消费线程处理正常,或 LIS 已经成功接收消息。状态更新 SQL 出现过,也不等于每次都更新成功或实际影响了记录。

默认配置下,语句摘要保存在内存中,汇总周期为 1800 秒;当前周期以外的数据要查 CLUSTER_STATEMENTS_SUMMARY_HISTORY,历史保留数量和摘要容量也有限。如果启用了语句摘要持久化,历史保存方式会变化。查询没有结果时,还要核对采集开关、时间范围和是否发生淘汰。

九、检查接口事务是否长时间未结束

接口程序如果把数据库事务和外部调用放在一起,LIS 响应慢时,数据库事务也可能一直没有结束。例如,程序开启事务后取出 100 条消息,把状态改成处理中,随后调用 LIS、等待响应,再更新状态并提交。这段等待时间需要和数据库里的事务持续时间对照。

SELECT
    INSTANCE,
    ID AS trx_id,
    START_TIME,
    TIMESTAMPDIFF(
        SECOND,
        START_TIME,
        NOW()
    ) AS trx_seconds,
    STATE,
    USER,
    DB,
    SESSION_ID,
    MEM_BUFFER_KEYS,
    MEM_BUFFER_BYTES,
    LEFT(
        CURRENT_SQL_DIGEST_TEXT,
        300
    ) AS current_sql
FROM information_schema.CLUSTER_TIDB_TRX
WHERE USER = 'hip_app'
ORDER BY START_TIME;

CLUSTER_TIDB_TRX 提供各 TiDB 节点当前事务的信息,包括开始时间、会话 ID、当前语句和内存缓冲区写入量。事务状态可能是 Idle、Running、LockWaiting、Committing 或 RollingBack。Idle 表示当前没有执行语句,不代表事务已经结束;CURRENT_SQL_DIGEST_TEXT 是归一化文本,也可能为 NULL。如果积压发生在更早的时间,还要对照当时的应用和数据库记录。

十、查看当前锁等待和保留的死锁记录

多个消费线程同时更新同一批消息时,还需要看锁等待。用 DATA_LOCK_WAITS 查看从 TiKV 收集到的当前等待信息,再根据等待事务和持锁事务继续定位。

SELECT
    TRX_ID,
    CURRENT_HOLDING_TRX_ID,
    SQL_DIGEST_TEXT,
    KEY_INFO
FROM information_schema.DATA_LOCK_WAITS;
字段 检查内容
TRX_ID 正在等待锁的事务 ID
CURRENT_HOLDING_TRX_ID 持有锁的事务 ID
SQL_DIGEST_TEXT 被阻塞语句的归一化文本,可能无法获取
KEY_INFO 被锁 Key 对应的表、索引或记录信息

空结果示例:

Empty set

这个结果只能记为“本次查询未返回锁等待记录”。DATA_LOCK_WAITS 实时向 TiKV 收集信息,各节点的数据也不保证来自同一时刻。它不保留历史等待,之前短暂发生的阻塞,事后可能查不到;集群较大或负载较高时,最好不要过于频繁查询这个表。

如果怀疑发生过死锁,再查最近一小时仍保留在 CLUSTER_DEADLOCKS 中的记录:

SELECT
    INSTANCE,
    DEADLOCK_ID,
    OCCUR_TIME,
    RETRYABLE,
    TRY_LOCK_TRX_ID,
    CURRENT_SQL_DIGEST_TEXT,
    TRX_HOLDING_LOCK
FROM information_schema.CLUSTER_DEADLOCKS
WHERE OCCUR_TIME >= NOW() - INTERVAL 1 HOUR
ORDER BY OCCUR_TIME DESC;

这条查询中的“一小时”只是过滤范围。默认情况下,每个 TiDB 节点只保留最近 10 个死锁事件,且不收集可重试死锁;节点重启后记录也不会保留。即使查询为空,也不能排除之前发生过死锁,更不能排除普通锁等待。

十一、回头检查轮询 SQL

前面的 7.5 系列已经讨论过接口轮询 SQL 和索引优化。这次查看轮询语句有没有变慢,保留原来的索引设计。Dashboard 概况页可以查看近期 SQL、慢查询和整体延迟;需要按库和表筛选时,我用 CLUSTER_SLOW_QUERY。

SELECT
    INSTANCE,
    TIME,
    USER,
    QUERY_TIME,
    PROCESS_KEYS,
    TOTAL_KEYS,
    DIGEST,
    LEFT(`QUERY`, 300) AS SQL_TEXT
FROM information_schema.CLUSTER_SLOW_QUERY
WHERE DB = 'hip_lab'
  AND TIME >= NOW() - INTERVAL 30 MINUTE
  AND `QUERY` LIKE '%hip_interface_message%'
ORDER BY QUERY_TIME DESC
LIMIT 20;

TiDB 8.5 的 tidb_slow_log_threshold 默认是 300 ms,现场要以实际配置为准;查询结果中的 QUERY_TIME 单位为秒。这里同样按默认库过滤,并通过语句文本匹配表名,必要时要调整筛选范围

十二、整理数据库侧的检查结果

结果示例按检查顺序放在一起,供后面核对接口日志时使用。

检查项 结果示例中的现象 还需要核对的内容
待发送消息 LIS 数量较多,连续观察时仍在增加 记录观察时间和最老消息时间,核对接口处理时限
失败和重试 LIS 有失败记录,部分消息有多次重试 同一消息的重试变化、失败发生时间和应用错误
数据库连接 hip_app 连接仍在 消费线程是否正常,之前是否发生过连接异常
近期 SQL 同类轮询和状态更新语句持续出现 执行账号、语句执行是否成功及业务处理结果
当前事务 未观察到明显长事务 更早时间段是否有事务未及时提交
锁等待和死锁 当前查询没有等待记录,保留的死锁记录中未查到相关信息 未被当前查询和保留记录覆盖的阻塞情况
轮询 SQL 未集中进入慢日志,原有组合索引仍被使用 日志配置、历史耗时和实际执行计划

按这组检查结果,暂时没有取得长事务、锁等待或轮询 SQL 明显变慢导致积压的证据。现实生产业务中,LIS 失败和重试消息还需要关注核对接口程序、网络及下游响应。

十三、提取能关联应用日志的消息

失败记录主要集中在 LIS 时,把对应消息编号和时间范围整理给接口开发或应用运维。下面只取排查需要的字段,不读取完整报文。

SELECT
    message_no,
    visit_no,
    message_type,
    retry_count,
    created_at,
    next_retry_at
FROM hip_lab.hip_interface_message
WHERE target_system = 'LIS'
  AND process_status = 9
ORDER BY created_at
LIMIT 50;

这条查询取最早的 50 条失败消息,message_no、消息类型、产生时间和重试次数可以用于查找对应应用日志,例如测试消息号 HIP202609172241580031。就诊号只在确有业务关联需要时使用;完整 HL7、XML 或 JSON 报文包含更多患者信息,不能为方便排查就整段复制、转发。

十四、继续检查接口程序和下游响应

数据库连接仍在、同类 SQL 也有执行记录时,把消息编号交给接口人员,对照发送线程、线程池和应用日志,确认消息在哪一步停住。需要查清楚请求有没有发出、TCP 或 HTTP 连接有没有报错,以及 LIS 返回了什么响应;消费并发是否调整、失败消息为什么持续重试,也要一并核对。

如果同一批消息的 retry_count 持续增加,失败又集中在 LIS,优先核对这批消息的发送和响应记录。失败状态可能在报文解析、连接下游或收到业务拒绝后写入,具体停在哪一步,不能只靠消息表判断。

十五、出现这些现象时,继续查数据库

应用侧的检查进行时,数据库侧仍按实际现象处理。下面几种情况需要继续定位,不能因为前一次查询没有异常就停止检查。

现象 数据库侧继续检查的内容
hip_app 无法建立连接 TiDB Server、代理入口、权限、连接数和客户端报错
多个事务长时间处于 LockWaiting 用 DATA_LOCK_WAITS 查等待事务和持锁事务,结合事务持续时间判断
接口 UPDATE 长时间不结束 对照 CLUSTER_PROCESSLIST、CLUSTER_TIDB_TRX 和语句摘要
轮询 SQL 频繁进入慢日志,或耗时明显偏离以往 检查执行计划、统计信息、扫描 Key 数和索引
多个业务系统同时出现数据库访问异常 扩大到 TiDB、PD、TiKV、监控和资源使用情况

0
3
3
3

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论