介绍
支持人员需要查看每个工单的时间线,还需要一份包含无事件工单的汇总。你将把活动记录与工单关联起来,编写可重复使用的 SQL 报表,并根据查询计划添加索引。数据集很小时响应看起来很快,不能作为访问路径高效的充分证据。
本独立实验提供前面教学中使用的基础工单结构。你将使用一个临时 D1 数据库,自行创建关联关系、报表和索引。
使用你自己的学习账户和全新的 VM。设置过程会先准备 Node.js 22.22.0,然后在 /home/labex/project/ticket-database 下运行 npm install,安装项目本地的 Wrangler 4.131.1 及评测所需的依赖。直接依赖的版本已固定;安装过程会创建自己的 lockfile。设置过程不会登录云端,也不会操作评测数据库。在个人计算机上,请在项目中运行 npm install --save-dev wrangler@4.131.1,安装相同版本的 Wrangler。
本实验使用 D1 免费额度内的小型合成记录。现有账户的使用量也会计入这些额度。无需购买域名。在确认资源已删除并完成退出登录之前,请保留此 VM。
授权此 VM 并选择账户
在此步骤中,你要将这个全新的终端连接到自己的学习账户。仅登录 Dashboard 并不会授权此 VM。D1 权限允许创建数据库、修改 SQL 和删除数据库。授权前请查看实际的同意页面,包括 Background Access。
打开准备好的项目并检查已固定版本的 CLI:
cd /home/labex/project/ticket-database
npx wrangler --version
预期输出为 4.131.1。启动设备授权;--device 会显示浏览器代码,--browser=false 会让你自行决定是否打开浏览器:
npx wrangler login --device --browser=false --scopes account:read user:read d1:write
在浏览器中打开显示的 URL,输入当前代码,确认学习账户和权限,然后完成授权。等待终端确认授权成功。不要将密码或令牌粘贴到项目文件中。
npx wrangler whoami --json
检查 loggedIn: true,然后读取账户的 name 和 id,即使列表中只有一个账户也要读取。将目标账户的 ID 复制到下面的配置中。下面的 Shell 变量使用 6 个随机字节(12 个十六进制字符),以避免与其他学习者发生冲突。Here-document 会将 JSON 两行之间的内容写入文件;其中的 $RUN 会展开。
$schema 前的反斜杠会保留这个 JSON 键的原样;$RUN 仍会展开为本次运行的唯一名称。
RUN=labex-c04-d04-$(openssl rand -hex 6)
cat > wrangler.jsonc <<JSON
{
"\$schema": "./node_modules/wrangler/config-schema.json",
"name": "$RUN",
"account_id": "YOUR_ACCOUNT_ID",
"main": "src/index.js",
"compatibility_date": "2026-09-15",
"workers_dev": true,
"preview_urls": false
}
JSON
运行这段代码前,先将 YOUR_ACCOUNT_ID 替换为实际账户 ID。保持此终端打开,以便 RUN 变量继续可用。name 用于标识本次运行;account_id 用于选择执行云端操作的账户。该文件是普通 JSON,同时也是有效的 JSONC。写入此文件不会部署 Worker。
将活动关联到工单
在此步骤中,你要添加一个活动表,用来表示某个工单的多个事件。外键会将活动记录的 ticket_id 关联到真实工单,从而防止事件指向不存在的父记录。设置过程会提供熟悉的工单结构,因此你可以专注于关联关系和查询。
创建一个临时云端数据库。--binding DB 为应用代码提供简短名称,--update-config 会将数据库的实际名称和 UUID 记录到 wrangler.jsonc,--use-remote=false 则让开发过程使用本地数据库:
npx wrangler d1 create "$RUN-db" --binding DB --update-config --use-remote=false
读取创建出的名称和 ID,然后查看已保存的绑定:
cat wrangler.jsonc
DB 条目必须指向本次运行创建的数据库。绑定是代码与资源之间配置好的连接。UUID 用于标识云端数据库,而 --local 使用此 VM 中独立的 SQLite 数据库。执行 SQL 命令时,始终明确使用 --local 或 --remote。
读取并在本地应用提供的工单结构:
cat schema.sql
npx wrangler d1 execute DB --local --file schema.sql
编写活动表结构和固定数据集。这里的整数时间戳是用于排序的合成值,不是当前时间:
cat > activity.sql <<'SQL'
CREATE TABLE activity (
id INTEGER PRIMARY KEY,
ticket_id INTEGER NOT NULL REFERENCES tickets(id),
action TEXT NOT NULL,
created_at INTEGER NOT NULL
);
INSERT INTO activity (id, ticket_id, action, created_at) VALUES
(1, 1, 'opened', 100),
(2, 1, 'assigned', 200),
(3, 2, 'opened', 110),
(4, 2, 'closed', 300);
INSERT INTO tickets (id, subject, source) VALUES (3, 'No activity yet', 'seed');
SQL
在本地应用这些内容:
npx wrangler d1 execute DB --local --file activity.sql
工单 1 和工单 2 各有两个事件;工单 3 没有事件。下一步将展示如何在汇总中包含这个零事件工单。
编写工单活动报表
在此步骤中,你要组合两个表中的记录。连接(join)会根据关联关系匹配记录。t 和 a 是表名的简短别名。LEFT JOIN 会保留每个工单,即使该工单没有活动;COUNT(a.id) 只统计匹配到的活动 ID。GROUP BY 会按工单汇总事件,AS event_count 则为计算出的列命名。
编写一个可重复使用的只读报表查询。将查询保存在 SQL 文件中,可以在本地和远程环境运行同一份报表:
cat > report.sql <<'SQL'
SELECT t.id, t.subject, COUNT(a.id) AS event_count
FROM tickets AS t
LEFT JOIN activity AS a ON a.ticket_id = t.id
GROUP BY t.id, t.subject
ORDER BY t.id;
SQL
运行报表:
npx wrangler d1 execute DB --local --file report.sql
预期工单 1、2、3 的计数分别为 2、2 和 0。如果使用内连接,工单 3 会被排除;如果统计 *,则会把它那条未匹配的占位记录也统计进去。读取一个工单按时间排序的事件:
npx wrangler d1 execute DB --local --command "SELECT action, created_at FROM activity WHERE ticket_id = 1 ORDER BY created_at;"
预期先看到 100 时的 opened,然后看到 200 时的 assigned。报表回答每个工单有多少个事件;筛选查询回答某个工单包含哪些事件。
使用查询计划说明索引的合理性
在此步骤中,你要为活动时间线添加查找结构。索引会按一定顺序存储可搜索的值,从而避免扫描无关记录。索引会占用存储空间,并在索引值发生变化时增加额外工作,因此应针对具体查询添加索引。
先在添加索引前查看查询计划。EXPLAIN QUERY PLAN 描述 SQLite 的访问策略;它不会对经过的时间进行基准测试:
npx wrangler d1 execute DB --local --command "EXPLAIN QUERY PLAN SELECT action, created_at FROM activity WHERE ticket_id = 1 ORDER BY created_at;"
查找对 activity 的扫描,以及可能用于排序的临时结构。创建一个组合索引,先使用 ticket_id,再使用 created_at:
cat > index.sql <<'SQL'
CREATE INDEX idx_activity_ticket_created ON activity(ticket_id, created_at);
SQL
npx wrangler d1 execute DB --local --file index.sql
npx wrangler d1 execute DB --local --command "EXPLAIN QUERY PLAN SELECT action, created_at FROM activity WHERE ticket_id = 1 ORDER BY created_at;"
此时查询计划应在搜索部分提到 idx_activity_ticket_created。具体格式可能不同。固定数据集太小,无法进行有意义的耗时比较;这里的证据是查询采用了所选访问路径。
创建索引后添加一个新事件,然后再次运行报表:
npx wrangler d1 execute DB --local --command "INSERT INTO activity (id, ticket_id, action, created_at) VALUES (5, 1, 'replied', 400);"
npx wrangler d1 execute DB --local --file report.sql
工单 1 现在有三个事件。索引不仅要支持读取,也必须保证写入结果正确。
在 D1 中运行报表和索引查询
在此步骤中,你要将已检查的结构和索引安装到远程实验数据库中。仅有本地文件不会在远程数据库中建立结构。
npx wrangler d1 execute DB --remote --file schema.sql
npx wrangler d1 execute DB --remote --file activity.sql
npx wrangler d1 execute DB --remote --file index.sql
出现提示时,只确认本实验的数据库。添加相同的最终活动,查看报表和远程查询计划:
npx wrangler d1 execute DB --remote --command "INSERT INTO activity (id, ticket_id, action, created_at) VALUES (5, 1, 'replied', 400);"
npx wrangler d1 execute DB --remote --command "$(cat report.sql)"
npx wrangler d1 execute DB --remote --command "EXPLAIN QUERY PLAN SELECT action, created_at FROM activity WHERE ticket_id = 1 ORDER BY created_at;"
$(cat report.sql) 会将报表文本作为查询传入。远程环境中的 --file 使用导入流程,并报告导入元数据,而不是 SELECT 结果表。远程计数应为 3、2 和 0,查询计划中应显示你的索引。这证明了 D1 上的查询结果和访问路径,但没有假设一定会改善某个特定的耗时。如有需要,可以在 Dashboard 中打开本次运行的 D1 数据库,进行只读的表结构检查;但应以 CLI 查询计划作为索引证据。

此示例显示五条远程活动记录及其 ticket_id 关联。生成的数据库名称标识本次示例运行,你的名称会不同。created_at 数字是用于排序的模拟值,不是当前时间戳。此表仅供直观查看记录;上方 CLI 的 EXPLAIN QUERY PLAN 输出确认索引的使用,并不证明耗时有所改善。
删除临时资源
在此步骤中,趁 VM 仍处于授权状态,只删除本实验的资源。请先完成所有功能检查。在确认删除完成前,保留配置文件。
npx wrangler d1 delete DB
查看提示,并确认只删除本次运行创建的数据库。然后列出数据库:
npx wrangler d1 list --json
在成功响应中,你记录的数据库名称和 UUID 必须不存在。其他资源可以保留。身份验证错误或网络错误不能作为结论:请先解决访问问题,然后重新执行读取操作再继续。仍处于登录状态时,完成此步骤的验证。
结束此 VM 的授权
在独立的删除检查通过后,才能执行此步骤结束授权。退出登录会删除此 VM 中保存的 Wrangler 授权信息;仅关闭 VM 并不会清理云端资源。
npx wrangler logout
npx wrangler whoami --json
预期输出为 loggedIn: false。未认证的查询可能以非零状态退出;只有当结构化响应明确表示你已退出登录时,这种结果才是预期行为。完成验证后,关闭实验环境。
总结
你练习了如何查询和索引工单活动。你检查了可观察的数据库结果,明确保留了所选账户和本地状态,并在退出登录前删除了临时资源。



