跳到正文
Agent 工程实践数据库设计与查询优化
返回课程/Agent 工程实践
第 9 / 15 章 约 8 分钟

数据库设计与查询优化

学习数据查询、索引、聚合和证据组织。

这个模块在 Agent 里的位置

这个模块负责 Agent 的数据读取链路,决定它能以什么成本、什么延迟、什么权限读到什么数据。

它影响:

  • Tool 的能力上限。
  • 查询延迟。
  • 上下文质量。
  • 多租户隔离。
  • 证据可回放性。
  • 系统成本。

一个 Agent 再聪明,如果数据库和查询设计只能全表扫描,也做不出可用的产品。

先记住几个概念

概念含义为什么重要
Raw eventappend-only 原始事实审计和回放
Rollup小时 / 天级聚合TopN 和趋势
Evidence window证据窗口还原上下文
Partition按时间分区控制扫描范围
Tenant-first index租户优先索引隔离和过滤
Query pushdown排序、聚合、limit 下推避免应用层扫全量
Coverage数据完整性状态防止把缺数据当成零

从一个真实问题开始:Agent 问了一个“看起来简单”的问题

用户问:

过去 30 天风险最高的 10 个实体是谁?

如果直接查 raw 表:

select *
from raw_event
where tenant_id = 7
order by occurred_at desc;

在以下规模下:

  • 每天 1 亿条事件
  • 30 天保留,约 30 亿行
  • 50 万实体
  • 2000 租户

会发生什么?

  1. 没有时间边界,可能扫 30 亿行。
  2. 没有实体过滤。
  3. 没有 topK。
  4. 没有聚合下推。
  5. 返回不可预算。
  6. Agent 上下文被 raw row 淹没。

正确问题不是“模型怎么查”,而是数据库和查询层应该为 Agent 提供什么查询原语。

三层数据结构

一个面向 Agent 的数据查询层通常分三层:

层表用途
原始层raw_eventappend-only 事实
聚合层risk_event_rollup_hourlyTopN / 趋势
证据层evidence_window还原上下文和引用

Agent 的查询顺序应该是:

先查聚合层
  -> 拿到实体 ID 和排名
  -> 再按 ID 查证据窗口
  -> 必要时才按 ID 精查 raw event

不要让 Agent 从 raw event 开始推理。

原始事件表

create table raw_event (
  id bigint,
  tenant_id bigint not null,
  session_id bigint not null,
  event_type text not null,
  entity_id bigint,
  payload jsonb not null,
  occurred_at timestamptz not null,
  primary key (id, occurred_at)
) partition by range (occurred_at);

索引:

create index raw_event_query_idx
  on raw_event (tenant_id, occurred_at, entity_id, event_type);

设计要点:

  1. 按时间分区,避免全表扫描。
  2. 索引以 tenant 开头。
  3. raw event 只追加,不原地修改。
  4. payload 保留原始事实,但不直接给模型。

小时聚合表

create table risk_event_rollup_hourly (
  tenant_id bigint not null,
  entity_id bigint not null,
  event_hour timestamp not null,
  event_count bigint not null,
  risk_score numeric not null,
  primary key (tenant_id, entity_id, event_hour)
);

create index risk_rollup_time_idx
  on risk_event_rollup_hourly (tenant_id, event_hour, entity_id);

TopN 查询:

select entity_id,
       sum(event_count) as event_count,
       sum(risk_score) as risk_score
from risk_event_rollup_hourly
where tenant_id = $1
  and event_hour >= $2
  and event_hour < $3
group by entity_id
order by risk_score desc
limit 10;

这条查询的关键点:

  1. tenant 由服务端注入。
  2. 时间范围必填。
  3. group by 和 order by 在数据库完成。
  4. limit 在数据库完成。

不要把 50 万实体拉回应用层排序。

证据窗口表

create table evidence_window (
  id bigint primary key,
  tenant_id bigint not null,
  session_id bigint not null,
  start_ts timestamptz not null,
  end_ts timestamptz not null,
  event_ids bigint[] not null,
  summary text not null
);

create index evidence_window_session_idx
  on evidence_window (tenant_id, session_id, start_ts desc);

用途:

  1. 给模型返回 evidenceWindowIds。
  2. 输出校验引用是否存在。
  3. 需要回放时按窗口还原事件。
  4. 避免把 eventIds 全量塞进上下文。

Tool 只暴露业务原语,不暴露 SQL

推荐的工具:

Tool作用
query_top_risk_entitiesTopN 聚合
query_evidence_windows证据窗口
query_metric_timeseries趋势
get_raw_event_by_id精查单条事实

不要提供:

{
    "name": "execute_sql",
    "input": {
        "sql": "select * from ..."
    }
}

原因:

  1. 模型不理解 schema 语义。
  2. 容易生成全表扫描。
  3. 权限无法收口。
  4. 输出不可预算。
  5. 审计困难。

返回结果必须带 coverage

一个 TopN 查询返回:

{
    "ok": true,
    "query": {
        "tenantId": 7,
        "startTs": 1790000000000,
        "endTs": 1790086400000,
        "groupBy": "entity",
        "topK": 10,
        "order": "risk_score_desc"
    },
    "resolvedRange": {
        "startTs": 1790000000000,
        "endTs": 1790086400000,
        "source": "risk_event_rollup_hourly"
    },
    "coverage": {
        "partitionComplete": true,
        "sampleCount": 182345,
        "partialWindow": false
    },
    "entities": [
        {
            "entityId": "entity-9182",
            "riskScore": 87.4,
            "eventCount": 12345,
            "evidenceWindowIds": ["win-1001", "win-1002"]
        }
    ],
    "totalEntities": 487320,
    "truncated": true,
    "nextCursor": "entity-cursor-9183"
}

coverage 的价值是防止模型把“没查到”当成“没有风险”。

常见工程错误

错误实际后果正确做法
直接查 raw 表延迟和成本失控先查 rollup
无时间分区30 亿行扫描按天分区
索引不以 tenant 开头跨租户扫描tenant-first index
TopN 在应用层算内存和网络浪费数据库 group by / order by / limit
返回 raw row上下文膨胀聚合 + evidence refs
无 coverage缺数据被当成零返回完整性和缺失状态
暴露 SQL 工具权限和成本不可控只暴露业务原语

上线前检查

  1. 时间范围必填。
  2. tenant scope 服务端注入。
  3. TopN 在数据库完成。
  4. 聚合表覆盖高频查询。
  5. raw 表只允许按 ID 精查。
  6. 分页有 cursor。
  7. 返回带 coverage。
  8. 查询延迟和扫描量可观测。