
Database Skill
- 38 installs
- 411 repo stars
- Updated August 4, 2026
- bytedance/agentkit-samples
database-skill is a Claude skill for metadata management, data analysis, development changes, and ops diagnosis across Volcengine and self-built MySQL/PostgreSQL and other databases.
About
This skill is a database assistant for Volcengine databases (MySQL, veDB-MySQL, PostgreSQL, SQL Server, MongoDB, Redis) and self-built MySQL and PostgreSQL. It covers metadata queries, data analysis with HTML reports, data governance, development change tickets, and ops diagnosis such as slow queries, deadlocks, and health inspection. It routes each request to a scenario reference file and enforces a strict approval flow for data changes, keeping execute_sql read-only.
- Manages Volcengine and self-built MySQL/PostgreSQL/SQL Server/MongoDB/Redis
- Covers metadata, BI analysis, dev change tickets, and ops diagnosis
- Enforces read-only execution and approval flows for writes
Database Skill by the numbers
- 38 all-time installs (skills.sh)
- Ranked #458 of 911 Databases skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
database-skill capabilities & compatibility
Requires Volcengine AK/SK; targets Volcengine or self-built databases.
- Works with
- mysql · postgres · sql server · mongodb · redis
- Use cases
- database · data analysis · debugging
- Runs
- Runs locally
- Pricing
- Bring your own API key
What database-skill says it does
🔴 **execute_sql 只能执行只读操作**(SELECT、SHOW、EXPLAIN)。**你绝不能通过 execute_sql 执行 INSERT/UPDATE/DELETE/DDL,无论平台是否实际拦截。**
不支持字节云(ByteCloud)数据库,如 ByteRDS / ByteDoc / ByteRedis。
npx skills add https://github.com/bytedance/agentkit-samples --skill database-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 38 |
|---|---|
| repo stars | ★ 411 |
| Last updated | August 4, 2026 |
| Repository | bytedance/agentkit-samples ↗ |
What it does
Manage, analyze, and diagnose Volcengine and self-built databases across metadata, BI, and ops scenarios.
Who is it for?
Metadata queries, BI analysis, and ops diagnosis on Volcengine and self-built MySQL/PostgreSQL databases.
Skip if: ByteCloud databases such as ByteRDS, ByteDoc, or ByteRedis, which are unsupported.
When should I use this skill?
Use for Volcengine or self-built database metadata, analysis, change tickets, or ops diagnosis.
What you get
- HTML visualization report and screenshot (BI)
- change ticket (dev changes)
- diagnostic recommendations (ops)
By the numbers
- supports 6 database engines
- 6 scenario routes in the router
- execute_sql truncates at 3000 rows
Files
Database Skill 核心指令
你是一名专业的数据库智能助手。你的目标是安全、准确、高效地执行数据库相关任务。
帮用户多想一步 — 不只完成任务,更提供专家洞察。结论先行:先说好还是不好,再说为什么。
🔴 核心原则 (必须遵守)
1. 安全第一: 涉及数据变更 (DML/DDL) 时,必须严格遵循审批流程,严禁直接执行高风险 SQL。 2. 场景路由: 收到用户请求后,立即根据「场景路由」判断使用哪个场景,并加载对应的参考文件。 3. 数据诚实: 绝不编造数据,图表不误导。 4. 必须使用指定工具链:
- 数据库操作必须通过 toolbox 函数,禁止直接用 pymysql / sqlalchemy 等连接数据库
- 本地文件分析(CSV / Excel / JSON / Parquet)必须通过
MultiSourceAnalyzer - 混合分析(数据库 + 文件)时,先用
query_sql()获取 DB 数据,再用MultiSourceAnalyzer联合分析
行为准则
1. 自主执行:用户给出了明确任务(如"帮我查一下"、"分析某表"),直接执行,不要停下来反复确认。只在真正缺少必要信息时才询问。 2. SOP 完整性:按 SOP 排查时,必须尝试所有步骤。某步调用失败或不支持时,明确说明跳过原因,不要默默跳过。 3. 不支持 ≠ 无结论:函数不支持或返回空数据时,必须给出替代方案或下一步建议,不能只说"不支持"就结束。 4. 诊断要深入:运维诊断场景,初始查询后应继续深入(如慢查询聚合 → 明细/趋势/优化建议)。 5. 趋势查询:用户要求"趋势"或"时间维度分析"时,SQL 必须包含时间维度的 GROUP BY(如按天/小时分组)。 6. 场景切换:用户话题发生实质改变时(如从"分析数据"→"为什么慢"→"加个字段"),必须回到场景路由表重新匹配,读取新场景的全部必读文件。 7. 产出必须交付:场景路由表的"产出"列是必需的交付物,不是可选的。数据分析必须产出 HTML 报告 + 截图,运维诊断必须给出明确行动建议,不能只返回原始数据就结束。 8. 运维诊断必须读 SOP:进入运维诊断场景后,必须先读取对应的场景 SOP 文件再开始排查。SOP 包含诊断路径、必看数据和根因知识,禁止跳过 SOP 仅凭经验诊断。 9. 尊重用户指定的数据库:用户在 prompt 中提到了具体数据库名(如"demo_refund 库"、"skill_test 里"),必须切换到该数据库操作。遇到错误时如实报告,禁止静默切换到其他数据库。
🔑 配置检查
凭证通过 create_client() 初始化时自动加载(优先级:环境变量 > skills/.env 文件)。
⚠️ 严禁直接操作 .env 文件
- 绝对禁止用 Write / Edit / shell 命令直接读写
.env文件 - 绝对禁止通过 shell 命令(如
echo $VOLCENGINE_ACCESS_KEY)检查凭证
正确方式
from toolbox import check_env, update_env, create_client
# 1. 检查凭证状态(不泄露实际值)
result = check_env()
# → {"success": True, "data": {"credentials_ready": True, "configured_keys": [...], "missing_keys": [...]}}
# 2. 若缺少配置,询问用户后安全更新
update_env(VOLCENGINE_ACCESS_KEY="xxx", VOLCENGINE_SECRET_KEY="yyy")仅当 check_env() 返回 credentials_ready: False 时,才询问用户提供缺失值。
🌏 支持的地域
用户提到地域时,根据下表映射为 RegionId 传给 create_client(region=...):
| 地域 | RegionId |
|---|---|
| 华东2(上海) | cn-shanghai |
| 华北2(北京/廊坊) | cn-beijing |
| 华南1(广州) | cn-guangzhou |
| 中国香港 | cn-hongkong |
| 亚太东南(柔佛) | ap-southeast-1 |
| 亚太东南(雅加达) | ap-southeast-3 |
用户未指定地域时不传 region,自动从环境变量 VOLCENGINE_REGION 读取。
---
🚦 场景路由 (Scenario Router)
根据用户意图,必须加载并遵循相应的参考文件:
| 用户意图 | 匹配场景 | 必须读取的文件(函数名和参数在文件中) | 产出 |
|---|---|---|---|
| "有哪些表?"<br>"表结构是什么?" | 元数据探查 | references/api/metadata-query.md | 表结构信息 |
| "盘点数据资产"<br>"检查数据质量"<br>"查敏感数据" | 数据治理 | 按需读取 references/metadata/*.md | 治理报告 |
| "查下最近订单"<br>"统计销售额"<br>"分析数据趋势" | 数据分析 (BI) | references/analysis/index.md<br>references/api/metadata-query.md | HTML 可视化报告 + 截图 |
| "删除数据"<br>"加个字段"<br>"建表""改表" | 开发变更 (Dev) | references/develop/index.md | 变更工单 |
| "巡检一下"<br>"做个健康检查" | 巡检 | references/ops/health-inspection.md<br>references/api/ops.md | 巡检概览报告 |
| "为什么慢?"<br>"有报错吗?"<br>"排查性能问题" | 运维诊断 (Ops) | ① references/ops/index.md → 按症状匹配场景 SOP<br>② 对应的场景 SOP 文件(如 mysql/slow-query.md)<br>③ references/api/ops.md(函数参数、过滤、翻页) | 诊断建议 |
---
执行方式
必须从 `scripts/` 目录执行,否则 import 会失败。
🔴 纯函数式 API — 所有函数的第一个参数是client,用function(client, ...)调用。
禁止client.function(...)写法,client没有这些方法,会报AttributeError。
cd skills/database-skill/scripts && python3 -c "
from toolbox import create_client, list_tables
import json
client = create_client()
result = list_tables(client, instance_id='xxx', database='yyy', fetch_all=True)
print(json.dumps(result, indent=2, ensure_ascii=False))
"工作流
1. 从用户问题中提取 instance_id、database、region(地域)等参数
- 用户给出的值像 instance_id(如
mysql-xxx、pg-xxx、vedbm-xxx)→ 直接用instance_id=传给后续函数,无需先搜索 - 用户给出的是实例名称 → 用
list_instances(instance_name=名称)按名称搜索 - 不确定是 ID 还是名称 → 用
list_instances(query=关键词)搜索 - 用户提到了地域(如"上海的实例"、"广州区域")→ 传
region给create_client()
2. create_client(region=...) 创建客户端(自动从环境变量加载凭证,支持中文地域名) 3. 调用具体函数,传入 client + 业务参数 4. 检查返回值的 `success` 字段,利用 context 中已解析的参数透传给后续调用
返回格式与 context
所有函数返回 {success, message, data, context}。必须先检查 `success`,再使用 `data`。
success: true→ 正常使用datasuccess: false+error.missing→ 缺参数,向用户询问后补全重试success: false+ 实例不存在 → 立即告知用户,禁止自动换实例重试
`context` 包含 instance_id、database、instance_type、region。 下一次调用时直接透传 context 中的值,避免重复解析:
# 上一步输出了 context: {"instance_id": "xxx", "database": "mydb", "instance_type": "MySQL", "region": "cn-beijing"}
# 本步直接用 context 的值:
info = get_table_info(client, table="users", instance_id="xxx", database="mydb")数据查询
两种方式可选,Agent 自行判断:
- nl2sql:
list_tables→nl2sql(query, tables=[...])→execute_sql。步骤少、速度快,但 SQL 可能有字段名偏差。 - 查询 schema 后自写 SQL:
list_tables→get_table_info→ 根据真实字段名自行编写 SQL →execute_sql/query_sql。步骤多,但 SQL 更精准。
例外:SHOW TABLES / SHOW CREATE TABLE / EXPLAIN 等固定语句,或用户给出了完整 SQL,直接执行。
🔴 execute_sql 只能执行只读操作(SELECT、SHOW、EXPLAIN)。你绝不能通过 execute_sql 执行 INSERT/UPDATE/DELETE/DDL,无论平台是否实际拦截。 写操作必须通过工单函数,这是安全红线。
>
⚠️ 3000 行截断:execute_sql/query_sql单次最多返回 3000 行,超出部分静默截断(不报错)。返回恰好 3000 行 = 数据被截断,绝不能当作真实总数。 需要真实计数时必须用SELECT COUNT(*)。
>
⚠️ 空结果 ≠ 数据库存在:list_tables对不存在的数据库可能返回success: true+ 空列表,而非报错。当返回 0 张表时,应通过list_databases确认数据库是否真实存在,再向用户报告。
---
参数说明
参数补全规则:instance_id、database不传则从create_client()的默认值读取(来自环境变量)。
instance_type由代码根据instance_id自动解析,Agent 无需传递。
大数据量截断:聚合慢查询等返回列表较多时,data中会包含truncated: true和artifact_path(完整数据的临时 JSON 文件)。当truncated=true时,根据任务判断是否需要完整数据:定位 Top 问题用 inline 数据即可;全量统计时读取artifact_path文件。
数据库类型注意事项
| 类型 | 注意事项 |
|---|---|
| Postgres | schema 参数必传;SQL 需用 <schema>.<table> 写法 |
| MongoDB | execute_sql 使用 Mongo 语法(如 db.collection.find({}));nl2sql 生成 Pipeline 需指定 tables 参数;无固定 schema |
| Redis | execute_sql 使用 Redis 命令(如 INFO server);database 须传数字 0-15;无库表概念 |
| SQL Server / External | 仅支持元数据探查和数据查询,不支持运维诊断、监控和工单。External instance_id 以 External- 开头 |
---
🚨 错误处理
错误转译原则
- 禁止向用户透出 HTTP 状态码、堆栈等技术细节(RequestId 可以保留,便于排查)
- 必须将错误翻译为用户可理解的语言
实例指定原则
- 当用户明确指定了实例,禁止在操作失败后自动切换到其他实例
- 必须将错误原因如实告知用户,由用户决定下一步操作
错误处理表
| 错误情况 | 处理方式 |
|---|---|
CreateSessionError | 告知用户「当前账号无权访问该实例或实例不可用」,建议联系实例管理员添加权限 |
| 用户指定的实例或数据库操作失败 | 禁止自动切换到其他实例或数据库,如实告知错误原因 |
nl2sql 生成的 SQL 有误 | 用 get_table_info 获取真实字段名后自行编写 SQL |
缺少 instance_id | 必须先调用 list_instances() 或 list_databases() 探查,不可瞎编 |
工单状态 TicketPreCheck | 提示用户稍后查询详情 |
工单状态 TicketExamine | 提供审批链接,告知用户需要审批 |
| 执行 SQL 被安全规则拦截 | 自动创建相应工单 |
| INSERT / UPDATE / DELETE | 禁止 execute_sql() 直接执行,必须通过 create_dml_sql_change_ticket() |
| ALTER TABLE / DROP / CREATE | 禁止 execute_sql() 直接执行,必须通过 create_ddl_sql_change_ticket() |
| 实例类型不支持工单(如 SQL Server) | 生成 SQL 交给用户,告知「此实例不支持自动变更,请通过其他工具手动执行」 |
---
⚠️ 必须询问用户的情况
- 字段含义不明(无法从字段名/注释判断业务含义)
- 多个表都相关(不确定该查哪个表)
- 列值取值不明(英文值无法对应业务含义)
- 术语不熟悉(成功率指的是什么?)
- 缺少必要参数(无法推断 instance_id、database 等)
Reference 目录
函数名、完整参数、返回格式均在参考文件中。 本文件不列出函数签名,执行操作前必须先读取对应文件。
| 场景 | 文件 | 内容 |
|---|---|---|
| 元数据 / 数据查询 | references/api/metadata-query.md | list_instances, list_tables, execute_sql, nl2sql 等 10+ 函数:完整参数、返回格式、3000 行截断、翻页 |
| 数据治理 | references/metadata/*.md | 资产盘点、数据画像、数据质量、Schema 审计、敏感数据(按需读取具体文件) |
| 数据分析 | references/analysis/index.md | 7 步分析工作流、数据获取策略、多源联合、报告生成 |
| 开发变更 | references/develop/index.md | DML/DDL 工单流程、变更函数参数 |
| 运维诊断 — 场景路由 | references/ops/index.md | 按 db_type + 症状 → 对应 SOP 文件(诊断路径、必看数据、根因知识) |
| 运维诊断 — 函数参数 | references/api/ops.md | describe_slow_logs, list_connections 等 20+ 运维函数:完整参数、过滤条件(database/用户/IP)、翻页、返回格式 |
[project]
name = "database-skill"
version = "0.1.2"
description = "Database Skill"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"duckdb>=1.0.0",
"pandas>=2.0.0",
"openpyxl>=3.1.0",
"pyarrow>=14.0.0",
"playwright>=1.40.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
数据分析
核心原则
1. 先理解,后执行:动手前先理解用户真实需求和数据环境。 2. 专家视角:从数据分析师角度提供专业分析,不只返回查询结果。 3. 多想一步:主动发现异常、趋势和洞察,不仅回答字面问题。 4. 数据诚实:绝不编造数据,图表不误导。如实呈现异常值和缺失值。 5. 结论先行:先说好还是不好,再说为什么。
全局要求
- 语言适配:跟随用户输入语言(中文→全流程中文,English→English)
- 核心产出:每次完整分析必须包含 ① HTML 交互报告 ② PNG 静态截图 ③ 结构化文字结论
- 生成报告前必须问:受众是谁?用途是什么?
函数参数、数据库兼容性、返回格式详见 api/metadata-query.md。
工作流(7 步)
| 步骤 | 目标 | 关键 API | 输出 |
|---|---|---|---|
| 1. 数据探查 | 理解数据环境,确定目标表和字段 | list_tables, get_table_info, query_sql(LIMIT 10) | 表结构、字段含义、样例数据 |
| 2. 数据获取 | 获取原始数据 | query_sql / execute_sql / nl2sql + MultiSourceAnalyzer | DataFrame / CSV |
| 3. 质量检查 | 检查缺失值、重复行、异常值 | pandas: isnull(), duplicated(), describe() | 质量问题列表 |
| 4. EDA 分析 | 探索性分析,发现规律和异常 | pandas 聚合 + 框架分析 | 统计指标、趋势、对比 |
| 5. 结论提炼 | 输出结构化结论 | — | 核心洞察 + 行动建议 |
| 6. 报告生成 | HTML 可视化报告 + PNG 截图 | Write 工具 + Playwright | analysis_report.html + .png |
| 7. 交付 | 展示 PNG + HTML 链接给用户 | — | 最终回复 |
自主执行原则:用户给出了明确分析任务时(如"帮我分析某表"),自主选表、自主构造 SQL 并执行。只在以下阻断点才询问:字段含义不明、找不到目标表、列值无法对应业务含义。
趋势查询:用户要求"趋势"、"变化"、"时间维度"时,SQL 必须包含时间列的 GROUP BY(如 GROUP BY DATE(create_time)),输出时间序列数据。不能只返回总量统计。
数据探查策略
探查顺序:实例 → 数据库 → 表 → 字段 → 样例数据。
from toolbox import create_client, list_instances, list_databases, list_tables, get_table_info, query_sql
client = create_client()
# 找实例
instances = list_instances(client, instance_name="my_instance")
# 找数据库
dbs = list_databases(client, instance_id="mysql-xxx")
# 找表
tables = list_tables(client, instance_id="mysql-xxx", database="company", fetch_all=True)
# 查结构 + 样例
schema = get_table_info(client, table="orders", instance_id="mysql-xxx", database="company")
sample = query_sql(client, sql="SELECT * FROM orders LIMIT 10", instance_id="mysql-xxx", database="company")数据获取
3000 行截断规则(必须理解)
query_sql / execute_sql 单次最多返回 3000 行,超出部分静默截断(不报错、不提示)。
⚠️ 返回 3000 行 = 数据被截断,绝不能把 3000 当作真实总数。
需要真实计数时,必须用 SELECT COUNT(*) FROM table WHERE ...。查询策略(必须遵守)
| 场景 | 做法 |
|---|---|
| 需要总数/聚合指标 | 必须用聚合 SQL:SELECT COUNT(*), SUM(), AVG() 等,在数据库端完成计算 |
| 小表(确认 < 3000 行) | 一条 SQL 拉原始数据,pandas 本地 groupby |
| 大表多维度分析 | SQL 端聚合:SELECT a, b, COUNT(*), SUM(x) GROUP BY a, b,1-2 条 SQL 覆盖所有维度 |
| ❌ 禁止 | 用返回行数当总数;每个维度单独发一条 SQL |
大表防护(防止超时)
聚合 SQL 也可能触发全表扫描导致超时。查询前必须先评估表大小和索引:
# 1. 查表大小
from toolbox import create_client, execute_sql
client = create_client()
row_count = execute_sql(client, sql="SELECT COUNT(*) as cnt FROM my_table", database="mydb")
# 2. 查索引
index_info = execute_sql(client, sql="SHOW CREATE TABLE my_table", database="mydb")
plan = execute_sql(client, sql="EXPLAIN SELECT ...", database="mydb")按表大小选策略:
| 表大小 | 有索引覆盖 WHERE/GROUP BY | 无索引 |
|---|---|---|
| < 10 万行 | 直接查 | 直接查 |
| 10 万 ~ 500 万行 | 直接聚合 | 加 WHERE 缩小范围(时间、主键区间) |
| > 500 万行 | 直接聚合 | 必须加 WHERE 缩小范围,或分段采样 |
三种查询方式
1. nl2sql:nl2sql(client, query, tables=[...]) → execute_sql(client, sql=...)。快但可能有字段偏差。 2. 查 schema 后自写 SQL:get_table_info → 根据真实字段名写 SQL → query_sql。更精准。 3. 直接执行:用户给了完整 SQL,或 SHOW TABLES / EXPLAIN 等固定语句。
多数据源联合(MultiSourceAnalyzer)
数据分散在多个数据库或需要 DB + 文件联合分析时使用。
from toolbox import create_client, query_sql
from multi_source_analyzer import MultiSourceAnalyzer
analyzer = MultiSourceAnalyzer()
# 注册数据库查询结果
client = create_client()
df_orders = query_sql(client, sql="SELECT * FROM orders LIMIT 1000", database="company")
analyzer.register_dataframe('orders', df_orders)
# 注册本地文件(路径替换为用户实际文件)
analyzer.register_file('sales', '<path/to/sales.csv>')
analyzer.register_file('products', '<path/to/products.xlsx>', sheet='Product Inventory')
# 跨源 SQL 联合查询(基于 DuckDB)
result = analyzer.query("""
SELECT o.order_id, s.region, s.target
FROM orders o JOIN sales s ON o.region = s.region_code
""")| 方法 | 说明 |
|---|---|
register_dataframe(name, df) | 注册 DataFrame |
register_file(name, path, sheet=None) | 注册文件(CSV/Excel/JSON/Parquet) |
list_sources() | 查看已注册数据源 |
preview(name, n=5) | 预览数据 |
describe(name) | 查看结构 |
query(sql, limit=100) | 执行跨源 SQL |
AnalysisWorkflow(跨执行持久化)
每次 python3 -c "..." 都是独立进程,变量全部丢失。AnalysisWorkflow 将中间结果保存到磁盘。
何时用:需要 2 步以上的分析任务(探查→查询→分析→报告)。单步查询不需要。
按需持久化:核心需要持久化的:① 原始数据 ② 最终报告(HTML + PNG)。同一个 python3 -c 中能连续完成的步骤不需要分开持久化。
from analysis_workflow import create_workflow, resume_workflow
# 创建
wf = create_workflow()
print(wf.get_analysis_id())
# 恢复
wf = resume_workflow("analysis_20260308_143022")
# 保存/加载
wf.save_step_output("02_acquisition", "raw_data.csv", df)
df = wf.load_step_output("02_acquisition", "raw_data.csv")
# 报告路径
html_path = wf.get_output_path("06_report", "analysis_report.html")| 方法 | 说明 |
|---|---|
create_workflow() | 创建工作区 |
resume_workflow(analysis_id) | 恢复工作区 |
wf.save_step_output(step, filename, data) | 保存(JSON/CSV) |
wf.load_step_output(step, filename) | 加载 |
wf.get_output_path(step, filename) | 获取输出文件路径 |
工作区目录
| 步骤 | 目录 | 典型输出 |
|---|---|---|
| 数据探查 | 01_exploration/ | tables.json |
| 数据获取 | 02_acquisition/ | raw_data.csv |
| 质量检查 | 03_quality/ | quality_report.json |
| EDA 分析 | 04_eda/ | metrics.json |
| 结论提炼 | 05_conclusion/ | conclusion.md |
| 报告生成 | 06_report/ | analysis_report.html, .png |
报告生成
详见 report.md。
报告生成
报告风格
5 种经实战验证的视觉风格。用户未指定时随机选择。
| 风格 | 标志元素 | 最适场景 | 核心配色 |
|---|---|---|---|
| Financial Times | 三文鱼粉底 + 4px蓝色顶线 | 金融分析、叙事报告 | 背景 #FFF1E5,强调蓝 #0F5499 |
| McKinsey Consulting | 深蓝Header + Exhibit编号 | 战略分析、框架评估 | Header #003366,强调蓝 #4472C4,强调橙 #ED7D31 |
| The Economist | 6px红色顶线 + editorial标题 | 行业洞察、观点报告 | 红 #E3120B,白底 |
| Goldman Sachs | Rating徽章 + 金色强调 | 财务建模、估值报告 | 深蓝 #00338D,金 #D4AF37 |
| Swiss / NZZ | 黑白灰红 + 72px大字 | 数据展示、设计感报告 | 黑 #000000,红 #FF0000,Helvetica |
风格要点:
- McKinsey:结论式标题(「Cloud revenue drives 60%」而非「Revenue by segment」),无渐变/阴影/圆角
- Economist:Editorial 标题带观点(「AI的胃口」而非「资本开支趋势」)
- Swiss:极端字号对比,禁止圆角/阴影/渐变/背景色块
模板选型
每个模板单独一个文件在 templates/ 下,生成报告时合并为完整 HTML。
| 内容类型 | 推荐模板 | 文件 |
|---|---|---|
| 数据概览/周报开头 | KPI看板 | template-kpi.md |
| 多维度对比 | 对比表格 | template-table.md |
| 趋势展示 | 柱状图 / 折线图 | template-bar.md / template-line.md |
| 占比分析 | 饼图/环形图 | template-pie.md |
| 转化分析 | 漏斗图 | template-funnel.md |
| 目标达成 | 进度条 | template-progress.md |
| 排名榜单 | 排名榜单 | template-rank.md |
| 问题诊断 | 诊断卡片 | template-diagnosis.md |
| 多板块报告 | 多Tab切换 | template-tabs.md |
生成流程
1. 合并模板为完整 HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { margin: 0; padding: 40px; background: #F5E6D3; font-family: "PingFang SC", Arial, sans-serif; }
.section { margin-bottom: 40px; background: #FFFDF7; border: 4px solid #1A1A1A; border-radius: 12px; padding: 30px; box-shadow: 6px 6px 0 #1A1A1A; }
</style>
</head>
<body>
<div class="section"><!-- KPI HTML --></div>
<div class="section"><!-- 表格 HTML --></div>
</body>
</html>2. 保存 HTML
HTML 较长时(超过 50 行),用 Write 工具直接写入 wf.get_output_path("06_report", "analysis_report.html") 返回的路径。
⚠️ 禁止把大段 HTML 塞进 python3 -c "..." 命令行。Shell 有长度限制。3. Playwright 截图
npx playwright screenshot "file://${HTML_PATH}" "${PNG_PATH}" \
--viewport-size=1200,675 --full-page --wait-for-timeout=30004. 交付
展示 PNG 截图 + HTML 文件链接给用户。
配色速查
| 用途 | 色值 |
|---|---|
| 珊瑚(主色) | #E17055 |
| 薄荷绿 | #45B7AA |
| 橄榄绿 | #5B8C5A |
| 金色(暗金) | #D4A017 |
| 天蓝 | #4ECDC4 |
| 暗橙 | #F39C12 |
模板3:柱状图
尺寸:1200×500 | 风格:Warm Narrative
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 500px; background: #FDF6EC;
padding: 40px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 700; color: #3D3D3D; margin-bottom: 6px; }
.subtitle { font-size: 14px; color: #999; margin-bottom: 30px; }
.chart-area {
background: #FFFFFF; border: 1px solid #E8DDD0;
border-radius: 16px; padding: 30px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
height: 340px; position: relative;
}
.bar-chart { display: flex; align-items: flex-end; gap: 20px; height: 250px; padding-top: 20px; }
.bar-group { flex: 1; display: flex; flex-direction: column; align-items: center; }
.bar { width: 60px; border-radius: 8px 8px 0 0; }
.bar-label { font-size: 13px; color: #888; margin-top: 8px; }
.bar-value { font-size: 12px; font-weight: 700; color: #3D3D3D; margin-bottom: 4px; }
.baseline {
position: absolute; left: 30px; right: 30px; bottom: 70px;
border-bottom: 2px dashed #E8DDD0;
}
.baseline-label {
position: absolute; right: 30px; bottom: 73px;
font-size: 11px; color: #E17055;
}
</style>
</head>
<body>
<p class="title">各板块ROI表现</p>
<p class="subtitle">2026年1月 | 目标线:3.0</p>
<div class="chart-area">
<div class="baseline"></div>
<p class="baseline-label">目标 3.0</p>
<div class="bar-chart">
<div class="bar-group">
<p class="bar-value">3.8</p>
<div class="bar" style="height: 190px; background: #4CAF50;"></div>
<p class="bar-label">美妆</p>
</div>
<div class="bar-group">
<p class="bar-value">2.5</p>
<div class="bar" style="height: 125px; background: #45B7AA;"></div>
<p class="bar-label">食品</p>
</div>
<div class="bar-group">
<p class="bar-value">1.1</p>
<div class="bar" style="height: 55px; background: #FF3B4F;"></div>
<p class="bar-label">服饰</p>
</div>
</div>
</div>
</body>
</html>---
适用场景
- 趋势展示
- 类别对比
---
使用方法
1. 复制 HTML 代码 2. 替换柱状图数据(调整 height 百分比) 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板4:问题诊断卡片
尺寸:1200×700 | 风格:Neo-Brutalism
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 700px; background: #F5E6D3;
padding: 30px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 20px; }
.cards { display: flex; gap: 20px; }
.diagnosis-card {
flex: 1; border: 4px solid #1A1A1A; border-radius: 12px;
overflow: hidden; box-shadow: 6px 6px 0 #1A1A1A;
}
.card-header { padding: 16px 20px; }
.header-red { background: #FF3B4F; }
.header-gold { background: #FFD700; }
.header-green { background: #4CAF50; }
.card-header h2 { color: #1A1A1A; font-size: 18px; font-weight: 800; }
.header-red h2 { color: #FFFDF7; }
.card-body { background: #FFFDF7; padding: 20px; }
.card-body ul { list-style: none; padding: 0; }
.card-body li {
font-size: 14px; color: #1A1A1A; line-height: 1.8;
padding: 6px 0; border-bottom: 1px solid #F0E8DD;
}
.card-body li:last-child { border-bottom: none; }
.priority-tag {
display: inline-block; background: #1A1A1A; color: #FFD700;
padding: 2px 8px; border-radius: 4px; font-size: 11px;
font-weight: 700; margin-right: 6px;
}
</style>
</head>
<body>
<p class="title">投放问题诊断与优化建议</p>
<div class="cards">
<div class="diagnosis-card">
<div class="card-header header-red"><h2>问题(需立即处理)</h2></div>
<div class="card-body">
<ul>
<li><p><span class="priority-tag">P0</span>服饰板块ROI 1.1,低于盈亏线</p></li>
<li><p><span class="priority-tag">P0</span>退货率45%导致实际GMV缩水近半</p></li>
</ul>
</div>
</div>
<div class="diagnosis-card">
<div class="card-header header-gold"><h2>建议(本周执行)</h2></div>
<div class="card-body">
<ul>
<li><p><span class="priority-tag">1</span>服饰板块暂停ROI<1的计划</p></li>
<li><p><span class="priority-tag">2</span>美妆板块预算上调20%</p></li>
</ul>
</div>
</div>
<div class="diagnosis-card">
<div class="card-header header-green"><h2>亮点(继续保持)</h2></div>
<div class="card-body">
<ul>
<li><p>美妆ROI 3.8,超目标27%</p></li>
<li><p>食品退货率仅15%</p></li>
</ul>
</div>
</div>
</div>
</body>
</html>---
适用场景
- 问题诊断
- 优化建议展示
---
使用方法
1. 复制 HTML 代码 2. 替换问题/建议/亮点内容 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板:下钻分析报告(Drill-Down)
尺寸:1200×auto(自适应高度) | 风格:Neo-Brutalism
用于展示逐层深入的分析过程:全局概览 → 维度拆解 → 交叉下钻 → 根因建议。
每层之间用引导条连接,说明"发现了什么 → 接下来分析什么"。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; background: #F5E6D3;
padding: 40px; font-family: "PingFang SC", Arial, sans-serif;
}
/* ── 报告标题 ── */
.report-header {
text-align: center; margin-bottom: 36px;
}
.report-title {
font-size: 28px; font-weight: 900; color: #1A1A1A;
letter-spacing: 2px;
}
.report-subtitle {
font-size: 14px; color: #888; margin-top: 6px;
}
/* ── 层级标签 ── */
.level-section { margin-bottom: 0; }
.level-tag {
display: inline-block;
background: #1A1A1A; color: #FFD700;
padding: 4px 14px; border-radius: 6px;
font-size: 13px; font-weight: 800;
margin-bottom: 10px; letter-spacing: 1px;
}
/* ── 卡片容器 ── */
.section-card {
background: #FFFDF7; border: 4px solid #1A1A1A;
border-radius: 12px; padding: 28px;
box-shadow: 6px 6px 0 #1A1A1A;
}
.section-title {
font-size: 20px; font-weight: 800; color: #1A1A1A;
margin-bottom: 18px;
}
/* ── 下钻引导条 ── */
.drilldown-connector {
display: flex; align-items: center; justify-content: center;
padding: 18px 0; position: relative;
}
.connector-line {
width: 4px; height: 30px; background: #E17055;
position: absolute; top: 0; left: 50%;
}
.connector-arrow {
width: 0; height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 12px solid #E17055;
position: absolute; bottom: 24px; left: calc(50% - 6px);
}
.connector-text {
background: #E17055; color: #FFFDF7;
padding: 6px 20px; border-radius: 20px;
font-size: 13px; font-weight: 700;
position: relative; top: 16px;
box-shadow: 3px 3px 0 #1A1A1A;
}
/* ── L0: KPI 卡片行 ── */
.kpi-row { display: flex; gap: 16px; }
.kpi-card {
flex: 1; text-align: center; padding: 18px 12px;
border: 2px solid #E8E0D8; border-radius: 10px;
background: #FDFAF5;
}
.kpi-value { font-size: 36px; font-weight: 900; }
.kpi-label { font-size: 13px; color: #888; margin-top: 4px; }
.kpi-change { font-size: 12px; margin-top: 2px; }
.c-coral { color: #E17055; }
.c-mint { color: #45B7AA; }
.c-gold { color: #D4A017; }
.c-olive { color: #5B8C5A; }
.up { color: #4CAF50; }
.down { color: #FF3B4F; }
/* ── L1: 维度柱状图区 ── */
.dim-chart-area {
display: flex; gap: 20px; margin-top: 12px;
}
.dim-chart {
flex: 1; background: #FDFAF5; border: 2px solid #E8E0D8;
border-radius: 10px; padding: 18px;
}
.dim-chart-title {
font-size: 14px; font-weight: 700; color: #1A1A1A;
margin-bottom: 12px;
}
.bar-row {
display: flex; align-items: center; margin-bottom: 8px;
font-size: 13px;
}
.bar-label { width: 80px; color: #555; text-align: right; padding-right: 10px; }
.bar-track { flex: 1; height: 22px; background: #F0E8DD; border-radius: 4px; overflow: hidden; position: relative; }
.bar-fill { height: 100%; border-radius: 4px; }
.bar-value { width: 60px; text-align: right; font-weight: 700; padding-left: 8px; }
.fill-coral { background: #E17055; }
.fill-mint { background: #45B7AA; }
.fill-gold { background: #D4A017; }
.fill-olive { background: #5B8C5A; }
/* 异常标注 */
.anomaly-badge {
display: inline-block;
background: #FF3B4F; color: #fff;
font-size: 11px; font-weight: 700;
padding: 1px 6px; border-radius: 3px;
margin-left: 4px;
}
/* ── L2: 交叉分析热力格 ── */
.cross-grid {
display: grid; gap: 3px; margin-top: 12px;
}
.cross-cell {
padding: 10px; text-align: center; font-size: 13px;
font-weight: 700; border-radius: 4px; color: #1A1A1A;
}
.cross-header {
background: #1A1A1A; color: #FFD700;
font-size: 12px; font-weight: 800;
}
.heat-1 { background: #FDE8E1; }
.heat-2 { background: #F9C4B4; }
.heat-3 { background: #F09E87; }
.heat-4 { background: #E17055; color: #fff; }
.heat-5 { background: #C0392B; color: #fff; }
/* ── L3: 根因诊断卡 ── */
.root-cards { display: flex; gap: 16px; margin-top: 12px; }
.root-card {
flex: 1; border: 3px solid #1A1A1A; border-radius: 10px;
overflow: hidden; box-shadow: 4px 4px 0 #1A1A1A;
}
.root-header { padding: 12px 16px; font-size: 15px; font-weight: 800; }
.root-header-red { background: #FF3B4F; color: #fff; }
.root-header-green { background: #4CAF50; color: #fff; }
.root-header-blue { background: #45B7AA; color: #fff; }
.root-body { background: #FFFDF7; padding: 16px; }
.root-body li {
font-size: 13px; color: #333; line-height: 1.9;
list-style: none; padding: 4px 0;
border-bottom: 1px solid #F0E8DD;
}
.root-body li:last-child { border-bottom: none; }
.cause-tag {
display: inline-block; background: #1A1A1A; color: #FFD700;
padding: 1px 8px; border-radius: 3px;
font-size: 11px; font-weight: 700; margin-right: 4px;
}
</style>
</head>
<body>
<!-- ═══════════ 报告标题 ═══════════ -->
<div class="report-header">
<p class="report-title">营销渠道 ROI 下钻分析</p>
<p class="report-subtitle">数据范围:2025-07 ~ 2025-12 | 数据源:MySQL + PostgreSQL + Excel</p>
</div>
<!-- ═══════════ Level 0: 全局概览 ═══════════ -->
<div class="level-section">
<span class="level-tag">L0 全局概览</span>
<div class="section-card">
<p class="section-title">核心 KPI</p>
<div class="kpi-row">
<div class="kpi-card">
<p class="kpi-value c-coral">¥228万</p>
<p class="kpi-label">总营收</p>
<p class="kpi-change up">↑ 34% MoM</p>
</div>
<div class="kpi-card">
<p class="kpi-value c-mint">5,000</p>
<p class="kpi-label">总订单</p>
<p class="kpi-change up">↑ 22%</p>
</div>
<div class="kpi-card">
<p class="kpi-value c-gold">2.8</p>
<p class="kpi-label">整体 ROI</p>
<p class="kpi-change up">↑ 0.3</p>
</div>
<div class="kpi-card">
<p class="kpi-value c-olive">¥732</p>
<p class="kpi-label">客单价</p>
<p class="kpi-change down">↓ 5%</p>
</div>
</div>
</div>
</div>
<!-- ── 下钻引导 ── -->
<div class="drilldown-connector">
<div class="connector-line"></div>
<div class="connector-arrow"></div>
<span class="connector-text">发现:整体 ROI 2.8,但各渠道差异大 → 按渠道拆解</span>
</div>
<!-- ═══════════ Level 1: 维度拆解 ═══════════ -->
<div class="level-section">
<span class="level-tag">L1 维度拆解</span>
<div class="section-card">
<p class="section-title">各渠道 ROI 对比</p>
<div class="dim-chart-area">
<!-- 左图:渠道 ROI -->
<div class="dim-chart">
<p class="dim-chart-title">ROI 排名</p>
<div class="bar-row">
<span class="bar-label">短视频</span>
<div class="bar-track"><div class="bar-fill fill-coral" style="width:92%"></div></div>
<span class="bar-value c-coral">4.6 <span class="anomaly-badge">↑ 最高</span></span>
</div>
<div class="bar-row">
<span class="bar-label">直播带货</span>
<div class="bar-track"><div class="bar-fill fill-mint" style="width:74%"></div></div>
<span class="bar-value">3.7</span>
</div>
<div class="bar-row">
<span class="bar-label">社交媒体</span>
<div class="bar-track"><div class="bar-fill fill-gold" style="width:60%"></div></div>
<span class="bar-value">3.0</span>
</div>
<div class="bar-row">
<span class="bar-label">内容种草</span>
<div class="bar-track"><div class="bar-fill fill-olive" style="width:52%"></div></div>
<span class="bar-value">2.6</span>
</div>
<div class="bar-row">
<span class="bar-label">搜索引擎</span>
<div class="bar-track"><div class="bar-fill fill-coral" style="width:44%"></div></div>
<span class="bar-value">2.2</span>
</div>
<div class="bar-row">
<span class="bar-label">邮件营销</span>
<div class="bar-track"><div class="bar-fill fill-mint" style="width:30%"></div></div>
<span class="bar-value">1.5</span>
</div>
<div class="bar-row">
<span class="bar-label">线下活动</span>
<div class="bar-track"><div class="bar-fill fill-gold" style="width:22%"></div></div>
<span class="bar-value down">1.1 <span class="anomaly-badge">↓ 最低</span></span>
</div>
</div>
<!-- 右图:渠道获客成本 -->
<div class="dim-chart">
<p class="dim-chart-title">获客成本 (CAC)</p>
<div class="bar-row">
<span class="bar-label">线下活动</span>
<div class="bar-track"><div class="bar-fill fill-coral" style="width:95%"></div></div>
<span class="bar-value down">¥320 <span class="anomaly-badge">↑ 最高</span></span>
</div>
<div class="bar-row">
<span class="bar-label">邮件营销</span>
<div class="bar-track"><div class="bar-fill fill-mint" style="width:72%"></div></div>
<span class="bar-value">¥245</span>
</div>
<div class="bar-row">
<span class="bar-label">搜索引擎</span>
<div class="bar-track"><div class="bar-fill fill-gold" style="width:55%"></div></div>
<span class="bar-value">¥186</span>
</div>
<div class="bar-row">
<span class="bar-label">内容种草</span>
<div class="bar-track"><div class="bar-fill fill-olive" style="width:42%"></div></div>
<span class="bar-value">¥142</span>
</div>
<div class="bar-row">
<span class="bar-label">社交媒体</span>
<div class="bar-track"><div class="bar-fill fill-coral" style="width:35%"></div></div>
<span class="bar-value">¥118</span>
</div>
<div class="bar-row">
<span class="bar-label">直播带货</span>
<div class="bar-track"><div class="bar-fill fill-mint" style="width:25%"></div></div>
<span class="bar-value">¥85</span>
</div>
<div class="bar-row">
<span class="bar-label">短视频</span>
<div class="bar-track"><div class="bar-fill fill-gold" style="width:18%"></div></div>
<span class="bar-value up">¥62 <span class="anomaly-badge">↓ 最低</span></span>
</div>
</div>
</div>
</div>
</div>
<!-- ── 下钻引导 ── -->
<div class="drilldown-connector">
<div class="connector-line"></div>
<div class="connector-arrow"></div>
<span class="connector-text">发现:短视频 ROI 最高(4.6)、线下活动最低(1.1) → 交叉渠道×地区深入</span>
</div>
<!-- ═══════════ Level 2: 交叉下钻 ═══════════ -->
<div class="level-section">
<span class="level-tag">L2 交叉下钻</span>
<div class="section-card">
<p class="section-title">渠道 × 地区 ROI 热力图</p>
<div class="cross-grid" style="grid-template-columns: 100px repeat(5, 1fr);">
<!-- 表头 -->
<div class="cross-cell cross-header"></div>
<div class="cross-cell cross-header">华东</div>
<div class="cross-cell cross-header">华南</div>
<div class="cross-cell cross-header">华北</div>
<div class="cross-cell cross-header">西南</div>
<div class="cross-cell cross-header">华中</div>
<!-- 短视频 -->
<div class="cross-cell cross-header">短视频</div>
<div class="cross-cell heat-5">5.8</div>
<div class="cross-cell heat-4">4.9</div>
<div class="cross-cell heat-3">3.8</div>
<div class="cross-cell heat-4">4.5</div>
<div class="cross-cell heat-3">4.0</div>
<!-- 直播带货 -->
<div class="cross-cell cross-header">直播带货</div>
<div class="cross-cell heat-4">4.2</div>
<div class="cross-cell heat-3">3.5</div>
<div class="cross-cell heat-3">3.6</div>
<div class="cross-cell heat-2">2.8</div>
<div class="cross-cell heat-3">3.1</div>
<!-- 社交媒体 -->
<div class="cross-cell cross-header">社交媒体</div>
<div class="cross-cell heat-3">3.4</div>
<div class="cross-cell heat-3">3.2</div>
<div class="cross-cell heat-2">2.5</div>
<div class="cross-cell heat-2">2.6</div>
<div class="cross-cell heat-2">2.8</div>
<!-- 线下活动 -->
<div class="cross-cell cross-header">线下活动</div>
<div class="cross-cell heat-1">1.4</div>
<div class="cross-cell heat-1">1.2</div>
<div class="cross-cell heat-1">0.8</div>
<div class="cross-cell heat-1">0.9</div>
<div class="cross-cell heat-1">1.0</div>
</div>
</div>
</div>
<!-- ── 下钻引导 ── -->
<div class="drilldown-connector">
<div class="connector-line"></div>
<div class="connector-arrow"></div>
<span class="connector-text">发现:短视频×华东 ROI 达 5.8,线下×华北仅 0.8 → 分析根因与建议</span>
</div>
<!-- ═══════════ Level 3: 根因与建议 ═══════════ -->
<div class="level-section">
<span class="level-tag">L3 根因 & 建议</span>
<div class="section-card">
<p class="section-title">诊断结论与行动建议</p>
<div class="root-cards">
<div class="root-card">
<div class="root-header root-header-red">根因分析</div>
<div class="root-body">
<ul>
<li><span class="cause-tag">主因</span>短视频渠道年轻用户占比 72%,复购率高</li>
<li><span class="cause-tag">主因</span>线下活动场地成本占预算 60%,转化周期长</li>
<li><span class="cause-tag">辅因</span>华东地区电商渗透率领先,线上渠道天然优势</li>
</ul>
</div>
</div>
<div class="root-card">
<div class="root-header root-header-blue">数据佐证</div>
<div class="root-body">
<ul>
<li><span class="cause-tag">数据</span>短视频渠道 18-30 岁用户占 72%,高于均值 45%</li>
<li><span class="cause-tag">数据</span>线下活动 CPA ¥320,是短视频的 5.2 倍</li>
<li><span class="cause-tag">数据</span>华东短视频转化率 8.3%,全国均值 5.1%</li>
</ul>
</div>
</div>
<div class="root-card">
<div class="root-header root-header-green">行动建议</div>
<div class="root-body">
<ul>
<li><span class="cause-tag">P0</span>短视频预算上调 30%,重点投华东、西南</li>
<li><span class="cause-tag">P1</span>线下活动缩减至一线城市,预算降 40%</li>
<li><span class="cause-tag">P2</span>直播带货复制短视频打法,测试华东市场</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</body>
</html>---
适用场景
- 需要逐层深入的分析报告(营销归因、异常排查、业绩复盘)
- 需要展示"发现 → 深入 → 结论"的分析逻辑链
- 演示 AI 的分析推理过程
---
结构说明
| 层级 | 标签 | 用途 | 典型图表 |
|---|---|---|---|
| L0 全局概览 | level-tag | 顶层 KPI,一眼看全貌 | KPI 卡片 |
| L1 维度拆解 | level-tag | 按单一维度拆解,标注异常 | 柱状图 + anomaly-badge |
| L2 交叉下钻 | level-tag | 两个维度交叉,定位热点 | 热力格 / 分组柱状图 |
| L3 根因建议 | level-tag | 根因诊断 + 行动计划 | 诊断三栏卡片 |
层与层之间用 drilldown-connector 连接,.connector-text 写明下钻依据。
---
使用方法
1. 复制 HTML 代码 2. 根据实际分析结果替换每一层的数据 3. 重点修改 .connector-text:写清每一步的下钻发现和理由 4. L1 的 anomaly-badge 标注值得深入的异常点 5. L2 的热力格颜色用 heat-1 ~ heat-5(浅到深) 6. L3 的 cause-tag 标注根因权重(主因/辅因/数据/P0-P2) 7. 合成到总报告后截图
---
自定义层数
不是所有分析都需要 4 层。可以灵活裁剪:
| 场景 | 推荐层数 | 说明 |
|---|---|---|
| 简单趋势查询 | L0 + L1 | 概览 + 维度拆解即可 |
| 多维对比分析 | L0 + L1 + L2 | 加交叉下钻 |
| 完整归因分析 | L0 ~ L3 | 四层全用 |
| 问题排查 | L0 + L1 + L3 | 跳过 L2,直接定位根因 |
模板7:漏斗图
尺寸:1200×600 | 风格:Neo-Brutalism
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 600px; background: #F5E6D3;
padding: 40px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 40px; }
.funnel { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.funnel-step {
display: flex; align-items: center; justify-content: space-between;
padding: 20px 30px; border: 4px solid #1A1A1A;
border-radius: 8px; box-shadow: 4px 4px 0 #1A1A1A;
background: #FFFDF7;
}
.funnel-step-1 { width: 800px; }
.funnel-step-2 { width: 640px; }
.funnel-step-3 { width: 480px; }
.funnel-step-4 { width: 320px; }
.funnel-label { font-size: 18px; font-weight: 700; color: #1A1A1A; }
.funnel-value { font-size: 24px; font-weight: 900; }
.funnel-rate { font-size: 14px; color: #888; margin-right: 10px; }
</style>
</head>
<body>
<p class="title">用户转化漏斗</p>
<div class="funnel">
<div class="funnel-step funnel-step-1">
<span class="funnel-label">访问用户</span>
<span class="funnel-rate">100%</span>
<span class="funnel-value" style="color: #E17055;">50,000</span>
</div>
<div class="funnel-step funnel-step-2">
<span class="funnel-label">浏览商品</span>
<span class="funnel-rate">60%</span>
<span class="funnel-value" style="color: #45B7AA;">30,000</span>
</div>
<div class="funnel-step funnel-step-3">
<span class="funnel-label">加入购物车</span>
<span class="funnel-rate">25%</span>
<span class="funnel-value" style="color: #D4A017;">12,500</span>
</div>
<div class="funnel-step funnel-step-4">
<span class="funnel-label">完成订单</span>
<span class="funnel-rate">10%</span>
<span class="funnel-value" style="color: #5B8C5A;">5,000</span>
</div>
</div>
</body>
</html>---
适用场景
- 转化率分析
- 流程分析
---
使用方法
1. 复制 HTML 代码 2. 调整漏斗宽度和数值 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板12:热点图/热力图
尺寸:1200×600 | 风格:Neo-Brutalism
用于展示交叉分析结果,如:时段×品类、地区×品类
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 600px; background: #F5E6D3;
padding: 30px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 20px; }
.heatmap { background: #FFFDF7; border: 4px solid #1A1A1A; border-radius: 12px; padding: 20px; box-shadow: 6px 6px 0 #1A1A1A; }
.heatmap-grid {
display: grid;
grid-template-columns: 100px repeat(5, 1fr);
gap: 4px;
}
.heatmap-header {
padding: 12px; text-align: center; font-weight: 700; font-size: 14px;
background: #1A1A1A; color: #FFD700; border-radius: 4px;
}
.heatmap-row-label {
padding: 12px; display: flex; align-items: center; font-weight: 700; font-size: 14px;
background: #1A1A1A; color: #FFF; border-radius: 4px;
}
.heatmap-cell {
padding: 16px; text-align: center; font-weight: 700; font-size: 14px;
border-radius: 4px; color: #1A1A1A;
}
.heatmap-legend {
display: flex; justify-content: center; gap: 20px; margin-top: 20px;
}
.heatmap-legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; }
.heatmap-legend-color { width: 20px; height: 20px; border-radius: 4px; }
</style>
</head>
<body>
<p class="title">各品类时段销售热力图</p>
<div class="heatmap">
<div class="heatmap-grid">
<!-- 表头 -->
<div class="heatmap-header"></div>
<div class="heatmap-header">美妆</div>
<div class="heatmap-header">食品</div>
<div class="heatmap-header">服饰</div>
<div class="heatmap-header">数码</div>
<div class="heatmap-header">家居</div>
<!-- 第1行 -->
<div class="heatmap-row-label">上午</div>
<div class="heatmap-cell" style="background: #FFEBEE;">¥12万</div>
<div class="heatmap-cell" style="background: #FFCDD2;">¥18万</div>
<div class="heatmap-cell" style="background: #FFCDD2;">¥15万</div>
<div class="heatmap-cell" style="background: #FFA726;">¥25万</div>
<div class="heatmap-cell" style="background: #FFCC80;">¥22万</div>
<!-- 第2行 -->
<div class="heatmap-row-label">中午</div>
<div class="heatmap-cell" style="background: #FFA726;">¥28万</div>
<div class="heatmap-cell" style="background: #FF7043;">¥35万</div>
<div class="heatmap-cell" style="background: #FFEBEE;">¥10万</div>
<div class="heatmap-cell" style="background: #FFEBEE;">¥8万</div>
<div class="heatmap-cell" style="background: #FFCDD2;">¥16万</div>
<!-- 第3行 -->
<div class="heatmap-row-label">下午</div>
<div class="heatmap-cell" style="background: #FF7043;">¥42万</div>
<div class="heatmap-cell" style="background: #FFA726;">¥26万</div>
<div class="heatmap-cell" style="background: #FFCDD2;">¥14万</div>
<div class="heatmap-cell" style="background: #FFCDD2;">¥12万</div>
<div class="heatmap-cell" style="background: #FFA726;">¥30万</div>
<!-- 第4行 -->
<div class="heatmap-row-label">晚间</div>
<div class="heatmap-cell" style="background: #EF5350; color: #FFF;">¥55万</div>
<div class="heatmap-cell" style="background: #FF7043;">¥38万</div>
<div class="heatmap-cell" style="background: #FF7043;">¥32万</div>
<div class="heatmap-cell" style="background: #FFA726;">¥20万</div>
<div class="heatmap-cell" style="background: #FF7043;">¥40万</div>
</div>
<div class="heatmap-legend">
<div class="heatmap-legend-item">
<div class="heatmap-legend-color" style="background: #EF5350;"></div>
<span>高 (>40万)</span>
</div>
<div class="heatmap-legend-item">
<div class="heatmap-legend-color" style="background: #FF7043;"></div>
<span>中高 (30-40万)</span>
</div>
<div class="heatmap-legend-item">
<div class="heatmap-legend-color" style="background: #FFA726;"></div>
<span>中 (20-30万)</span>
</div>
<div class="heatmap-legend-item">
<div class="heatmap-legend-color" style="background: #FFCDD2;"></div>
<span>低 (10-20万)</span>
</div>
<div class="heatmap-legend-item">
<div class="heatmap-legend-color" style="background: #FFEBEE;"></div>
<span>极低 (<10万)</span>
</div>
</div>
</div>
</body>
</html>---
适用场景
- 交叉分析(时段×品类、地区×品类)
- 找出高/低组合
---
使用方法
1. 复制 HTML 代码 2. 替换数据值和背景颜色(颜色越深数值越高) 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板1:KPI指标看板(4指标)
尺寸:1200×400 | 风格:Neo-Brutalism
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 400px; background: #F5E6D3;
padding: 30px; font-family: "PingFang SC", Arial, sans-serif;
display: flex; flex-direction: column;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 20px; }
.row { display: flex; gap: 20px; flex: 1; }
.card {
flex: 1; background: #FFFDF7; border: 4px solid #1A1A1A;
border-radius: 12px; padding: 20px;
box-shadow: 6px 6px 0 #1A1A1A;
display: flex; flex-direction: column; justify-content: center;
text-align: center;
}
.card-value { font-size: 52px; font-weight: 900; }
.card-label { font-size: 16px; color: #888; margin-top: 6px; }
.card-change { font-size: 14px; margin-top: 4px; }
.v-coral { color: #E17055; }
.v-mint { color: #45B7AA; }
.v-gold { color: #D4A017; }
.v-olive { color: #5B8C5A; }
.up { color: #4CAF50; }
.down { color: #FF3B4F; }
</style>
</head>
<body>
<p class="title">2026年1月 投放数据概览</p>
<div class="row">
<div class="card">
<p class="card-value v-coral">3.2</p>
<p class="card-label">整体ROI</p>
<p class="card-change up">↑ 0.4 vs 上月</p>
</div>
<div class="card">
<p class="card-value v-mint">730万</p>
<p class="card-label">总GMV</p>
<p class="card-change up">↑ 12%</p>
</div>
<div class="card">
<p class="card-value v-gold">16%</p>
<p class="card-label">消耗占比</p>
<p class="card-change up">↓ 2pt</p>
</div>
<div class="card">
<p class="card-value v-olive">28%</p>
<p class="card-label">退货率</p>
<p class="card-change down">↑ 3pt</p>
</div>
</div>
</body>
</html>---
适用场景
- 数据概览 / 周报开头
- 关键指标展示
---
使用方法
1. 复制 HTML 代码 2. 替换数据值和标签 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板5:趋势折线图
尺寸:1200×500 | 风格:Warm Narrative
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 500px; background: #FDF6EC;
padding: 40px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 700; color: #3D3D3D; margin-bottom: 6px; }
.subtitle { font-size: 14px; color: #999; margin-bottom: 30px; }
.chart-area {
background: #FFFFFF; border: 1px solid #E8DDD0;
border-radius: 16px; padding: 30px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
height: 340px; position: relative;
}
.chart { display: flex; align-items: flex-end; gap: 10px; height: 220px; padding-top: 20px; position: relative; }
.line {
position: absolute; bottom: 20px; left: 50px; right: 30px; height: 180px;
}
.line-point { position: absolute; width: 10px; height: 10px; background: #E17055; border-radius: 50%; transform: translate(-50%, 50%); }
.line-label { font-size: 12px; color: #888; text-align: center; width: 40px; }
.grid-line { position: absolute; left: 50px; right: 30px; border-bottom: 1px dashed #E8DDD0; }
.grid-label { position: absolute; left: 10px; font-size: 11px; color: #999; transform: translateY(50%); }
.legend { display: flex; gap: 20px; margin-top: 20px; justify-content: center; }
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 13px; color: #666; }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
</style>
</head>
<body>
<p class="title">月度销售趋势</p>
<p class="subtitle">2025年1月 - 2025年12月</p>
<div class="chart-area">
<div class="grid-line" style="bottom: 100px;"><span class="grid-label">100万</span></div>
<div class="grid-line" style="bottom: 60px;"><span class="grid-label">60万</span></div>
<div class="grid-line" style="bottom: 20px;"><span class="grid-label">20万</span></div>
<div class="chart">
<div class="line">
<div class="line-point" style="left: 5%; bottom: 30%;"></div>
<div class="line-point" style="left: 14%; bottom: 45%;"></div>
<div class="line-point" style="left: 23%; bottom: 40%;"></div>
<div class="line-point" style="left: 32%; bottom: 55%;"></div>
<div class="line-point" style="left: 41%; bottom: 50%;"></div>
<div class="line-point" style="left: 50%; bottom: 70%;"></div>
<div class="line-point" style="left: 59%; bottom: 65%;"></div>
<div class="line-point" style="left: 68%; bottom: 80%;"></div>
<div class="line-point" style="left: 77%; bottom: 75%;"></div>
<div class="line-point" style="left: 86%; bottom: 90%;"></div>
<div class="line-point" style="left: 95%; bottom: 85%;"></div>
</div>
<div class="line-label">1月</div>
<div class="line-label">2月</div>
<div class="line-label">3月</div>
<div class="line-label">4月</div>
<div class="line-label">5月</div>
<div class="line-label">6月</div>
<div class="line-label">7月</div>
<div class="line-label">8月</div>
<div class="line-label">9月</div>
<div class="line-label">10月</div>
<div class="line-label">11月</div>
<div class="line-label">12月</div>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background: #E17055;"></div>销售额</div>
</div>
</div>
</body>
</html>---
适用场景
- 月度/季度趋势展示
- 时间序列分析
---
使用方法
1. 复制 HTML 代码 2. 替换数据点位置(调整 bottom 百分比) 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板11:地图
尺寸:1200×600 | 风格:Neo-Brutalism
基于中国地图的 regional 分布展示,以下只是按照中国地图为例子,如果是其他地区或者则按需去生成
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 600px; background: #F5E6D3;
padding: 30px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 20px; }
.map-container { display: flex; gap: 30px; }
.map-area { flex: 2; background: #FFFDF7; border: 4px solid #1A1A1A; border-radius: 12px; padding: 20px; box-shadow: 6px 6px 0 #1A1A1A; position: relative; height: 480px; }
.map-legend { flex: 1; }
.map-bar {
display: flex; align-items: center; margin-bottom: 16px; padding: 12px;
background: #FFFDF7; border: 2px solid #1A1A1A; border-radius: 8px;
}
.map-bar-name { width: 80px; font-weight: 700; }
.map-bar-visual { flex: 1; height: 20px; background: #E8DDD0; border-radius: 4px; overflow: hidden; margin: 0 10px; }
.map-bar-fill { height: 100%; border-radius: 4px; }
.map-bar-value { width: 60px; text-align: right; font-weight: 700; }
</style>
</head>
<body>
<p class="title">各地区销售分布</p>
<div class="map-container">
<div class="map-area">
<!-- 使用 SVG 中国地图 -->
<svg viewBox="0 0 800 600" style="width: 100%; height: 100%;">
<!-- 地图区域 - 用色块表示 -->
<rect x="50" y="200" width="120" height="100" fill="#E17055" opacity="0.8"/>
<rect x="180" y="180" width="100" height="80" fill="#45B7AA" opacity="0.8"/>
<rect x="290" y="160" width="140" height="120" fill="#4CAF50" opacity="0.8"/>
<rect x="440" y="140" width="160" height="100" fill="#D4A017" opacity="0.8"/>
<rect x="610" y="120" width="100" height="80" fill="#E17055" opacity="0.8"/>
<!-- 标签 -->
<text x="110" y="255" text-anchor="middle" fill="#FFF" font-size="14" font-weight="700">华北</text>
<text x="230" y="225" text-anchor="middle" fill="#FFF" font-size="14" font-weight="700">华东</text>
<text x="360" y="225" text-anchor="middle" fill="#FFF" font-size="14" font-weight="700">华南</text>
<text x="520" y="195" text-anchor="middle" fill="#FFF" font-size="14" font-weight="700">西南</text>
<text x="660" y="165" text-anchor="middle" fill="#FFF" font-size="14" font-weight="700">西北</text>
</svg>
</div>
<div class="map-legend">
<div class="map-bar">
<span class="map-bar-name">华东</span>
<div class="map-bar-visual"><div class="map-bar-fill" style="width: 85%; background: #4CAF50;"></div></div>
<span class="map-bar-value">¥320万</span>
</div>
<div class="map-bar">
<span class="map-bar-name">华南</span>
<div class="map-bar-visual"><div class="map-bar-fill" style="width: 70%; background: #45B7AA;"></div></div>
<span class="map-bar-value">¥260万</span>
</div>
<div class="map-bar">
<span class="map-bar-name">华北</span>
<div class="map-bar-visual"><div class="map-bar-fill" style="width: 55%; background: #E17055;"></div></div>
<span class="map-bar-value">¥180万</span>
</div>
<div class="map-bar">
<span class="map-bar-name">西南</span>
<div class="map-bar-visual"><div class="map-bar-fill" style="width: 40%; background: #D4A017;"></div></div>
<span class="map-bar-value">¥120万</span>
</div>
<div class="map-bar">
<span class="map-bar-name">西北</span>
<div class="map-bar-visual"><div class="map-bar-fill" style="width: 25%; background: #E17055;"></div></div>
<span class="map-bar-value">¥80万</span>
</div>
</div>
</div>
</body>
</html>---
适用场景
- 地区销售分布
- 区域数据对比
---
使用方法
1. 复制 HTML 代码 2. 替换地图区域颜色和数据 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板6:饼图/环形图
尺寸:1200×500 | 风格:Neo-Brutalism
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 500px; background: #F5E6D3;
padding: 40px; font-family: "PingFang SC", Arial, sans-serif;
display: flex; gap: 60px; align-items: center;
}
.pie-container { width: 300px; height: 300px; position: relative; }
.pie {
width: 300px; height: 300px; border-radius: 50%;
background: conic-gradient(
#E17055 0deg 126deg,
#45B7AA 126deg 216deg,
#D4A017 216deg 288deg,
#5B8C5A 288deg 360deg
);
box-shadow: 6px 6px 0 #1A1A1A;
}
.pie-hole {
position: absolute; top: 50%; left: 50%;
width: 140px; height: 140px;
background: #F5E6D3; border-radius: 50%;
transform: translate(-50%, -50%);
display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.pie-hole-value { font-size: 28px; font-weight: 900; color: #1A1A1A; }
.pie-hole-label { font-size: 12px; color: #888; }
.legend { flex: 1; }
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 30px; }
.legend-item {
display: flex; align-items: center; gap: 16px;
padding: 16px 0; border-bottom: 2px solid #E8DDD0;
}
.legend-color { width: 24px; height: 24px; border-radius: 6px; border: 2px solid #1A1A1A; }
.legend-info { flex: 1; }
.legend-name { font-size: 16px; font-weight: 700; color: #1A1A1A; }
.legend-value { font-size: 20px; font-weight: 900; color: #1A1A1A; }
.legend-pct { font-size: 14px; color: #888; }
</style>
</head>
<body>
<div class="pie-container">
<div class="pie"></div>
<div class="pie-hole">
<p class="pie-hole-value">¥730万</p>
<p class="pie-hole-label">总销售额</p>
</div>
</div>
<div class="legend">
<p class="title">各品类销售占比</p>
<div class="legend-item">
<div class="legend-color" style="background: #E17055;"></div>
<div class="legend-info">
<p class="legend-name">美妆护肤</p>
</div>
<p class="legend-value">¥280万</p>
<p class="legend-pct">38%</p>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #45B7AA;"></div>
<div class="legend-info">
<p class="legend-name">食品饮料</p>
</div>
<p class="legend-value">¥180万</p>
<p class="legend-pct">25%</p>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #D4A017;"></div>
<div class="legend-info">
<p class="legend-name">服装配饰</p>
</div>
<p class="legend-value">¥150万</p>
<p class="legend-pct">20%</p>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #5B8C5A;"></div>
<div class="legend-info">
<p class="legend-name">其他</p>
</div>
<p class="legend-value">¥120万</p>
<p class="legend-pct">17%</p>
</div>
</div>
</body>
</html>---
适用场景
- 占比分析
- 品类分布
---
使用方法
1. 复制 HTML 代码 2. 调整 conic-gradient 角度分配占比 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板8:进度条/达成率
尺寸:1200×400 | 风格:Neo-Brutalism
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 400px; background: #F5E6D3;
padding: 40px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 30px; }
.progress-item { margin-bottom: 24px; }
.progress-header { display: flex; justify-content: space-between; margin-bottom: 8px; }
.progress-label { font-size: 16px; font-weight: 700; color: #1A1A1A; }
.progress-value { font-size: 16px; font-weight: 700; }
.progress-bar {
height: 32px; background: #FFFDF7; border: 3px solid #1A1A1A;
border-radius: 8px; overflow: hidden; position: relative;
}
.progress-fill { height: 100%; border-radius: 4px; }
.progress-target {
position: absolute; top: 0; bottom: 0; width: 3px; background: #1A1A1A;
}
.progress-target-label {
position: absolute; top: -20px; transform: translateX(-50%);
font-size: 11px; color: #888;
}
</style>
</head>
<body>
<p class="title">Q1 目标达成情况</p>
<div class="progress-item">
<div class="progress-header">
<span class="progress-label">GMV 目标 1000万</span>
<span class="progress-value" style="color: #4CAF50;">达成 85%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: 85%; background: #4CAF50;"></div>
<div class="progress-target" style="left: 80%;"><span class="progress-target-label">80%</span></div>
</div>
</div>
<div class="progress-item">
<div class="progress-header">
<span class="progress-label">新用户目标 10000人</span>
<span class="progress-value" style="color: #E17055;">达成 62%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: 62%; background: #E17055;"></div>
<div class="progress-target" style="left: 100%;"></div>
</div>
</div>
<div class="progress-item">
<div class="progress-header">
<span class="progress-label">活跃用户目标 50000人</span>
<span class="progress-value" style="color: #45B7AA;">达成 110%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: 100%; background: #45B7AA;"></div>
<div class="progress-target" style="left: 100%;"><span class="progress-target-label">100%</span></div>
</div>
</div>
</body>
</html>---
适用场景
- 目标达成情况
- KPI 进度展示
---
使用方法
1. 复制 HTML 代码 2. 调整进度条宽度(width %) 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板9:排名榜单
尺寸:1200×600 | 风格:Neo-Brutalism
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 600px; background: #F5E6D3;
padding: 40px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 20px; }
.rank-list { background: #FFFDF7; border: 4px solid #1A1A1A; border-radius: 12px; overflow: hidden; box-shadow: 6px 6px 0 #1A1A1A; }
.rank-item {
display: flex; align-items: center; padding: 16px 24px;
border-bottom: 2px solid #E8DDD0;
}
.rank-item:last-child { border-bottom: none; }
.rank-num {
width: 40px; height: 40px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 18px; font-weight: 900; margin-right: 20px;
}
.rank-num-1 { background: #FFD700; color: #1A1A1A; }
.rank-num-2 { background: #C0C0C0; color: #1A1A1A; }
.rank-num-3 { background: #CD7F32; color: #FFF; }
.rank-num-other { background: #E8DDD0; color: #1A1A1A; }
.rank-info { flex: 1; }
.rank-name { font-size: 16px; font-weight: 700; color: #1A1A1A; }
.rank-sub { font-size: 13px; color: #888; }
.rank-value { font-size: 20px; font-weight: 900; color: #1A1A1A; }
</style>
</head>
<body>
<p class="title">销售排行榜 TOP 10</p>
<div class="rank-list">
<div class="rank-item">
<div class="rank-num rank-num-1">1</div>
<div class="rank-info">
<p class="rank-name">北京市</p>
<p class="rank-sub">华北区域</p>
</div>
<p class="rank-value">¥128万</p>
</div>
<div class="rank-item">
<div class="rank-num rank-num-2">2</div>
<div class="rank-info">
<p class="rank-name">上海市</p>
<p class="rank-sub">华东区域</p>
</div>
<p class="rank-value">¥96万</p>
</div>
<div class="rank-item">
<div class="rank-num rank-num-3">3</div>
<div class="rank-info">
<p class="rank-name">广州市</p>
<p class="rank-sub">华南区域</p>
</div>
<p class="rank-value">¥85万</p>
</div>
<div class="rank-item">
<div class="rank-num rank-num-other">4</div>
<div class="rank-info">
<p class="rank-name">深圳市</p>
<p class="rank-sub">华南区域</p>
</div>
<p class="rank-value">¥72万</p>
</div>
<div class="rank-item">
<div class="rank-num rank-num-other">5</div>
<div class="rank-info">
<p class="rank-name">杭州市</p>
<p class="rank-sub">华东区域</p>
</div>
<p class="rank-value">¥65万</p>
</div>
</div>
</body>
</html>---
适用场景
- TOP N 排名
- 排行榜展示
---
使用方法
1. 复制 HTML 代码 2. 替换排名数据 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板2:多板块对比表格
尺寸:1200×600 | 风格:Neo-Brutalism
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 600px; background: #F5E6D3;
padding: 30px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 20px; }
table {
width: 100%; border-collapse: separate; border-spacing: 0;
border: 4px solid #1A1A1A; border-radius: 12px;
overflow: hidden; box-shadow: 6px 6px 0 #1A1A1A;
background: #FFFDF7;
}
th { background: #1A1A1A; color: #FFD700; padding: 14px 20px; font-size: 16px; font-weight: 700; text-align: left; }
td { padding: 14px 20px; font-size: 15px; color: #1A1A1A; border-bottom: 2px solid #E8DDD0; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: #FFF8E7; }
.tag { display: inline-block; padding: 3px 10px; border-radius: 6px; font-size: 13px; font-weight: 700; }
.tag-good { background: #D4EDDA; color: #155724; }
.tag-warn { background: #FFF3CD; color: #856404; }
.tag-bad { background: #F8D7DA; color: #721C24; }
.num-highlight { font-weight: 900; font-size: 18px; }
</style>
</head>
<body>
<p class="title">各板块ROI与盈亏分析</p>
<table>
<tr><th><p>板块</p></th><th><p>GMV</p></th><th><p>消耗</p></th><th><p>ROI</p></th><th><p>退货率</p></th><th><p>状态</p></th></tr>
<tr>
<td><p>美妆·护肤</p></td>
<td><p class="num-highlight">200万</p></td>
<td><p>45万</p></td>
<td><p class="num-highlight" style="color: #4CAF50;">3.8</p></td>
<td><p>28%</p></td>
<td><p><span class="tag tag-good">盈利</span></p></td>
</tr>
<tr>
<td><p>食品</p></td>
<td><p class="num-highlight">150万</p></td>
<td><p>30万</p></td>
<td><p class="num-highlight" style="color: #45B7AA;">2.5</p></td>
<td><p>15%</p></td>
<td><p><span class="tag tag-good">盈利</span></p></td>
</tr>
<tr>
<td><p>服饰</p></td>
<td><p class="num-highlight">300万</p></td>
<td><p>80万</p></td>
<td><p class="num-highlight" style="color: #FF3B4F;">1.1</p></td>
<td><p style="color: #FF3B4F; font-weight: 700;">45%</p></td>
<td><p><span class="tag tag-bad">亏损</span></p></td>
</tr>
</table>
</body>
</html>---
适用场景
- 多维度对比
- 数据明细展示
---
使用方法
1. 复制 HTML 代码 2. 替换表格数据 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板10:多Tab切换报告
尺寸:1200×700 | 风格:Neo-Brutalism
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 700px; background: #F5E6D3;
padding: 30px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 20px; }
.tabs { display: flex; gap: 8px; margin-bottom: 20px; }
.tab {
padding: 12px 24px; background: #FFFDF7; border: 3px solid #1A1A1A;
border-radius: 8px 8px 0 0; cursor: pointer; font-weight: 700;
color: #1A1A1A;
}
.tab.active { background: #1A1A1A; color: #FFD700; }
.content { background: #FFFDF7; border: 4px solid #1A1A1A; border-radius: 0 12px 12px 12px; padding: 30px; box-shadow: 6px 6px 0 #1A1A1A; }
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.stat-card { text-align: center; padding: 20px; background: #F5E6D3; border-radius: 8px; }
.stat-value { font-size: 32px; font-weight: 900; color: #E17055; }
.stat-label { font-size: 14px; color: #666; margin-top: 4px; }
</style>
</head>
<body>
<p class="title">综合运营报告</p>
<div class="tabs">
<div class="tab active">概览</div>
<div class="tab">销售</div>
<div class="tab">用户</div>
<div class="tab">库存</div>
</div>
<div class="content">
<div class="stat-grid">
<div class="stat-card">
<p class="stat-value">¥730万</p>
<p class="stat-label">总GMV</p>
</div>
<div class="stat-card">
<p class="stat-value">3.2</p>
<p class="stat-label">整体ROI</p>
</div>
<div class="stat-card">
<p class="stat-value">28%</p>
<p class="stat-label">退货率</p>
</div>
<div class="stat-card">
<p class="stat-value">50K</p>
<p class="stat-label">订单数</p>
</div>
</div>
</div>
</body>
</html>---
适用场景
- 综合报告
- 多板块数据展示
---
使用方法
1. 复制 HTML 代码 2. 替换 Tab 和内容数据 3. 保存为 HTML 文件 4. 合成到总报告后截图
模板13:词云
尺寸:1200×600 | 风格:Neo-Brutalism
用于展示关键词、标签、搜索热词等
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px; height: 600px; background: #F5E6D3;
padding: 30px; font-family: "PingFang SC", Arial, sans-serif;
}
.title { font-size: 24px; font-weight: 800; color: #1A1A1A; margin-bottom: 20px; }
.cloud-container {
background: #FFFDF7; border: 4px solid #1A1A1A; border-radius: 12px;
padding: 40px; box-shadow: 6px 6px 0 #1A1A1A;
height: 480px; position: relative; display: flex; flex-wrap: wrap;
align-items: center; justify-content: center; gap: 20px;
overflow: hidden;
}
.cloud-word {
display: inline-block; padding: 8px 16px; border-radius: 20px;
font-weight: 700; white-space: nowrap;
}
.cloud-1 { font-size: 56px; color: #E17055; }
.cloud-2 { font-size: 44px; color: #45B7AA; }
.cloud-3 { font-size: 36px; color: #D4A017; }
.cloud-4 { font-size: 28px; color: #5B8C5A; }
.cloud-5 { font-size: 22px; color: #888; }
</style>
</head>
<body>
<p class="title">用户搜索热词 TOP 20</p>
<div class="cloud-container">
<span class="cloud-word cloud-1">iPhone 15</span>
<span class="cloud-word cloud-2">手机壳</span>
<span class="cloud-word cloud-1">蓝牙耳机</span>
<span class="cloud-word cloud-3">充电宝</span>
<span class="cloud-word cloud-2">数据线</span>
<span class="cloud-word cloud-4">平板电脑</span>
<span class="cloud-word cloud-3">智能手表</span>
<span class="cloud-word cloud-5">键盘</span>
<span class="cloud-word cloud-2">笔记本电脑</span>
<span class="cloud-word cloud-4">鼠标</span>
<span class="cloud-word cloud-5">显示器</span>
<span class="cloud-word cloud-3">移动硬盘</span>
<span class="cloud-word cloud-5">U盘</span>
<span class="cloud-word cloud-4">音箱</span>
<span class="cloud-word cloud-5">路由器</span>
<span class="cloud-word cloud-3">耳机</span>
<span class="cloud-word cloud-4">相机</span>
<span class="cloud-word cloud-5">游戏机</span>
<span class="cloud-word cloud-5">散热器</span>
<span class="cloud-word cloud-5">支架</span>
</div>
</body>
</html>---
适用场景
- 搜索热词
- 关键词分析
- 标签分布
---
使用方法
1. 复制 HTML 代码 2. 替换关键词文字和大小级别(cloud-1 最大,cloud-5 最小) 3. 保存为 HTML 文件 4. 合成到总报告后截图
元数据探查与数据查询 API
数据库类型兼容性
| 函数 | MySQL | VeDB-MySQL | Postgres | SQL Server | MongoDB | Redis |
|---|---|---|---|---|---|---|
list_instances | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
list_databases | ✓ | ✓ | ✓ | ✓ | ⚠️ 仅总数 | ✗ |
list_tables | ✓ | ✓ | ✓ 需 schema | ✓ | ⚠️ 仅总数 | ✗ |
get_table_info | ✓ | ✓ | ✓ SQL 回退 | ✓ | ✗ | ✗ |
nl2sql | ✓ | ✓ | ✓ | ✓ | ⚠️ Pipeline | ✗ |
execute_sql | ✓ SQL | ✓ SQL | ✓ SQL | ✓ SQL | ✓ Mongo 语法 | ✓ Redis 命令 |
query_sql | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ |
说明:
✗表示不支持(调用时代码会自动拦截并返回错误)。instance_type由代码自动解析,无需手动传递。- MongoDB `execute_sql`:使用 Mongo 语法(如
db.getCollectionNames()、db.collection.find({})),不支持 SQL。 - Redis `execute_sql`:使用 Redis 命令(如
INFO server、GET key),database参数须传数字(0-15)。 - MongoDB `list_databases`/`list_tables`:API 返回
total计数但 Items 为 null(DBW 后端 bug),暂无法获取明细列表。可通过execute_sql执行db.getCollectionNames()替代。 - MongoDB `nl2sql`:生成 Pipeline 而非 SQL,必须通过
tables参数指定一个 collection 名,准确率较低。 - External 实例(
instance_id以External-开头):元数据和 SQL 查询正常,但不支持运维诊断和监控 API。
---
调用前提:
```python
cd skills/database-skill/scripts && python3 -c "
from toolbox import create_client, <函数名>
client = create_client(region='cn-beijing') # 可选参数见下方 create_client 说明
result = <函数名>(client, ...)
print(result)
"
```
>
`create_client` 参数(均可选,不传时从环境变量读取):
| 参数 | 说明 |
|:---|:---|
|region| 地域,如"cn-beijing"。环境变量VOLCENGINE_REGION|
|instance_id| 默认实例 ID。环境变量VOLCENGINE_INSTANCE_ID|
|database| 默认数据库名。环境变量VOLCENGINE_DATABASE|
>
下方函数签名中client参数类型为ToolboxClient(即create_client()返回值)。
元数据函数
list_instances
查询实例列表。必须传过滤项,不传等于查全量,无意义。
list_instances(client, # ToolboxClient: create_client() 返回值
instance_id=None, # str: 按实例 ID 搜索
query=None, # str: 模糊搜索(不确定是 ID 还是名称时用这个)
instance_name=None, # str: 按实例名称搜索
ds_type=None, # str: MySQL | Postgres | Mongo | Redis | MSSQL | VeDBMySQL | External
instance_status=None, # str: 状态过滤
page_number=1, # int
page_size=10, # int
)返回 data:
total: 匹配总数instances[]: 实例列表id,name,status,type(实例类型),version,region,zone,create_time- 托管实例额外字段:
endpoint,port,cpu,memory,storage - External 实例(
id以External-开头):无连接信息字段,DBW 代理连接
list_databases
列出实例下的数据库。
list_databases(client, # ToolboxClient: create_client() 返回值
instance_id=None, # str: 不传则用 create_client() 默认值
page_number=1, # int
page_size=10, # int
)返回 data:
total,pagedatabases[]:name,charset,collation,is_system,description
list_tables
列出数据库中的表。
list_tables(client, # ToolboxClient: create_client() 返回值
instance_id=None, # str
database=None, # str
schema=None, # str: Postgres 必传(默认查 public)
page_number=1, # int
page_size=50, # int
fetch_all=False, # bool: True 时自动翻页获取全部表
)返回 data:
total,database,schema(如有)tables[]: 表/集合列表
数据库差异:
- Postgres: 必须通过
schema参数指定 schema,不传默认查public - MongoDB: 返回的是集合(collection)列表
- Redis: 不支持
get_table_info
获取表结构(列名、类型、注释等)。
get_table_info(client, # ToolboxClient: create_client() 返回值
table="表名", # str: 必传
instance_id=None, # str
database=None, # str
schema=None, # str: Postgres 需要
)返回 data:
name,engine,charset,definitioncolumns[]:name,type,length,nullable,primary_key,auto_increment,default,comment
数据库差异:
- Postgres: 自动使用 SQL 回退(查
information_schema.columns),无法获取主键和自增信息 - MongoDB: 不支持(无固定 schema)
- Redis: 不支持
---
数据查询函数
nl2sql
自然语言转 SQL(仅生成,不执行)。
nl2sql(client, # ToolboxClient: create_client() 返回值
query="自然语言问题", # str: 必传
instance_id=None, # str
database=None, # str
tables=None, # list[str]: 指定参考表名,提升准确率
)最佳实践:先 list_tables 获取表名,传入 tables 参数缩小范围。生成的 SQL 可能有字段名偏差,需要校验后再执行。
数据库差异:
- MongoDB: 生成 Pipeline 而非 SQL,准确率较低
- Redis: 不支持
execute_sql
执行查询。MySQL / VeDB / PG / SQLServer 使用 SQL,MongoDB 使用 Mongo 语法,Redis 使用 Redis 命令。
execute_sql(client, # ToolboxClient: create_client() 返回值
sql="SELECT ...", # str: 必传(MongoDB 传 Mongo 语法,Redis 传 Redis 命令)
instance_id=None, # str
database=None, # str(Redis 须传数字 0-15)
)返回 data:
sql,state,row_count,columns[],rows[](每行是{列名: 值}字典)
限制:
- 最多返回 3000 行,超出静默截断。返回恰好 3000 行 = 数据被截断,需用
SELECT COUNT(*)获取真实计数 - INSERT / UPDATE / DELETE / DDL 会被拦截,必须走工单
数据库差异:
- Postgres: SQL 中表名需用
schema.table写法(如public.users) - MongoDB: 使用 Mongo 语法(如
db.getCollectionNames()、db.collection.find({})) - Redis: 使用 Redis 命令(如
INFO server、GET key),database参数须传数字(0-15)
query_sql
执行 SQL 返回 pandas DataFrame,是 execute_sql 的便捷封装。
query_sql(client, # ToolboxClient: create_client() 返回值
sql="SELECT ...", # str: 必传
instance_id=None, # str
database=None, # str
)返回: pandas.DataFrame(成功时)或 {"success": False, ...} 字典(失败时)
同样受 3000 行截断限制。适合需要后续用 pandas 分析的场景。
数据库差异:
- MongoDB / Redis: 不支持(无法转为 DataFrame)。MongoDB 可用
execute_sql获取数据后导出为 JSON/CSV 再用MultiSourceAnalyzer分析。
运维诊断 API
调用前提:
```python
cd skills/database-skill/scripts && python3 -c "
from toolbox import create_client, <函数名>
client = create_client(region='cn-beijing') # 可选参数见下方 create_client 说明
result = <函数名>(client, ...)
print(result)
"
```
>
`create_client` 参数(均可选,不传时从环境变量读取):
| 参数 | 说明 |
|:---|:---|
|region| 地域,如"cn-beijing"。环境变量VOLCENGINE_REGION|
|instance_id| 默认实例 ID。环境变量VOLCENGINE_INSTANCE_ID|
|database| 默认数据库名。环境变量VOLCENGINE_DATABASE|
数据库类型兼容性
| 函数 | MySQL | VeDB | PG | MongoDB |
|---|---|---|---|---|
describe_slow_logs | ✓ | ✓ | ✓ | ✓ |
describe_aggregate_slow_logs | ✓ | ✓ | ✓ | ✓ |
describe_slow_log_time_series_stats | ✓ | ✓ | ✓ | ✓ |
describe_full_sql_detail | ✓ | ✓ | ✓ | ✗ |
describe_health_summary | ✓ | ✓ | ✓ | ✗ |
get_metric_items / get_metric_data | ✓ | ✗ | ✗ | ✗ |
describe_table_metric | ✓ | ✓ | ✓ | ✗ |
list_connections | ✓ | ✓ | ✓ | ✓ |
kill_process | ✓ | ✓ | ✓ | ✓ |
describe_deadlock | ✓ | ✓ | ✗ | ✗ |
describe_trx_and_locks | ✓ | ✓ | ✓ | ✗ |
describe_lock_wait | ✓ | ✓ | ✓ | ✗ |
describe_err_logs | ✓ | ✓ | ✓ | ✗ |
describe_table_space | ✓ | ✓ | ✓ | ✗ |
describe_instance_nodes | ✓ | ✓ | ✓ | ✓ |
SQL Server、Redis、External 不支持运维诊断函数。不支持的类型调用时代码自动拦截。
大数据量截断:返回列表较多时,data中会包含truncated: true和artifact_path(完整数据的临时 JSON 文件)。定位 Top 问题用 inline 数据即可;全量统计时读取artifact_path。
---
慢查询与全量 SQL
describe_aggregate_slow_logs
按 SQL 模板聚合慢查询统计。推荐首选,比明细更适合定位 Top 问题。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
start_time | int | 是 | — | Unix 时间戳(秒) |
end_time | int | 是 | — | Unix 时间戳(秒) |
instance_id | str | 否 | 环境变量 | |
database | str | 否 | None | 按数据库名过滤 |
page_number | int | 否 | 1 | |
page_size | int | 否 | 10 | |
order_by | str | 否 | "TotalQueryTime" | TotalQueryTime / ExecuteCount |
sort_by | str | 否 | "DESC" | ASC / DESC |
users | list[str] | 否 | None | 按用户名过滤 |
source_ips | list[str] | 否 | None | 按来源 IP 过滤 |
keywords | list[str] | 否 | None | SQL 关键词过滤 |
tables | list[str] | 否 | None | 按表名过滤 |
min_query_time | float | 否 | None | 最小查询时间(秒) |
max_query_time | float | 否 | None | 最大查询时间(秒) |
node_id | str | 否 | None | VeDB 可指定节点 |
返回 data:
total: 总条数logs[]:sql_template,db,user,source_ip,execute_count,execute_count_ratio,query_time_stats(avg/max/min/total),lock_time_stats,rows_sent_stats,rows_examined_stats,first_appear_time,last_appear_time,sql_fingerprint,sql_method,table
describe_slow_logs
查询慢查询明细日志。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
start_time | int | 是 | — | |
end_time | int | 是 | — | |
instance_id | str | 否 | 环境变量 | |
database | str | 否 | None | 按数据库名过滤 |
page_number | int | 否 | 1 | |
page_size | int | 否 | 10 | |
order_by | str | 否 | "QueryTime" | |
sort_by | str | 否 | "DESC" | |
users | list[str] | 否 | None | 按用户名过滤 |
source_ips | list[str] | 否 | None | 按来源 IP 过滤 |
min_query_time | float | 否 | None | 最小查询时间(秒) |
max_query_time | float | 否 | None | 最大查询时间(秒) |
node_id | str | 否 | None |
返回 data:
total: 总条数logs[]:sql,template,query_time,lock_time,rows_scanned,rows_sent,timestamp,user,ip,database
describe_slow_log_time_series_stats
慢查询时间序列趋势。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
start_time | int | 是 | — | |
end_time | int | 是 | — | |
instance_id | str | 否 | 环境变量 | |
database | str | 否 | None | 按数据库名过滤 |
interval | int | 否 | 300 | 采样间隔(秒) |
min_query_time | float | 否 | None | 最小查询时间(秒) |
max_query_time | float | 否 | None | 最大查询时间(秒) |
node_id | str | 否 | None |
返回 data: slow_log_count_stats, cpu_usage_stats, interval
describe_full_sql_detail
查询完整 SQL 历史详情。支持游标翻页。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
start_time | int | 是 | — | Unix 时间戳(秒) |
end_time | int | 是 | — | Unix 时间戳(秒) |
instance_id | str | 否 | 环境变量 | |
database | str | 否 | None | 按数据库名过滤 |
page_size | int | 否 | 50 | |
users | list[str] | 否 | None | 按用户名过滤 |
source_ips | list[str] | 否 | None | 按来源 IP 过滤 |
keywords | list[str] | 否 | None | SQL 关键词过滤 |
tables | list[str] | 否 | None | 按表名过滤 |
sql_methods | list[str] | 否 | None | SQL 方法过滤(SELECT/INSERT/UPDATE/DELETE) |
min_exec_time | int | 否 | None | 最小执行时间(毫秒) |
max_exec_time | int | 否 | None | 最大执行时间(毫秒) |
context | str | 否 | None | 翻页游标(从上次返回的 data.context 获取) |
返回 data:
total,list_over(是否全部返回),context(翻页游标)sql_list[]:db_name,session_id,sql_type,query_string,exec_plan,start_timestamp,end_timestamp,exec_time,cpu_time,row_lock_wait_time,rows_examined,rows_sent,user_name,client_ip,sql_fingerprint,sql_table,node_id,sql_template
翻页:list_over=False时,将上次返回的data.context传入下次调用以获取下一页。list_over=True表示已无更多数据。
---
健康概览与监控
describe_health_summary
查询最近一小时的实例健康概览。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
end_time | int | 是 | — | Unix 时间戳(秒),返回截止到此时刻的最近一小时指标 |
instance_id | str | 否 | 环境变量 | |
node_ids | list[str] | 否 | None | 未传时自动查询 Primary 节点 |
diag_type | str | 否 | "ALL" |
返回 data:
instance_id,node_idsmetrics[]: 9 项指标name: CPU使用率 / QPS / TPS / innodbBufferPool命中率 / 内存使用率 / 当前打开连接数 / 慢查询数量 / 活跃会话数 / 连接数使用率- ⚠️ 连接数使用率 = 活跃会话数 / max_connections,不含 Sleep 连接。连接打满场景不能依赖此指标判断实际连接总数,需用
list_connections(show_sleep=True)获取。 - 当前打开连接数:包含所有状态的连接(含 Sleep),与「活跃会话数」(不含 Sleep)的区别在于是否计入空闲连接。
node_id: 节点 IDavg,max,min: 统计值total: 慢查询总数(仅慢查询数量指标)unit: 单位(%或空)mom: 环比变化率yoy: 同比变化率
get_metric_items / get_metric_data
监控指标查询。仅 MySQL。先调 get_metric_items 获取可用指标名,再用 get_metric_data 查数据。
get_metric_items(client, instance_id=None)
get_metric_data(client,
metric_name, # str: 指标名(从 get_metric_items 获取)
start_time, end_time, # int
instance_id=None,
period=60, # int: 采样周期(秒)
node_id=None,
)describe_table_metric
表级 DML/DDL 监控。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
db_name | str | 是 | — | 数据库名 |
table | str | 是 | — | 表名 |
start_time | int | 是 | — | |
end_time | int | 是 | — | |
instance_id | str | 否 | 环境变量 | |
table_sql_type | str | 否 | "DML" | "DML" 或 "DDL" |
---
会话与进程
list_connections
查询实时活跃会话列表。推荐用于巡检和会话排查。支持服务端筛选,可按用户、数据库、命令类型等条件过滤。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
instance_id | str | 否 | 环境变量 | |
show_sleep | bool | 否 | False | True 时包含 Sleep 连接。⚠️ 连接打满排查时必须设为 True,否则只返回活跃连接,无法看到空闲堆积的 Sleep 连接 |
page_number | int | 否 | 1 | |
page_size | int | 否 | 50 | |
node_id | str | 否 | None | 指定节点(VeDB 默认查所有节点) |
users | str \ | list[str] | 否 | None |
hosts | str \ | list[str] | 否 | None |
dbs | str \ | list[str] | 否 | None |
command_type | str | 否 | None | 按命令类型筛选(Query/Sleep 等) |
min_time | int | 否 | None | 最小执行时间(秒),筛选执行时间≥N秒的会话 |
sql | str | 否 | None | 按 SQL 语句匹配 |
fuzzy_match | bool | 否 | True | 模糊匹配(False 时精确匹配) |
返回 data:
total: 会话总数sessions[]:process_id,user,host,db,command,time(执行秒数),state,info(当前 SQL);VeDB 额外返回node_id,node_type
⚠️ 部分实例类型(如 VeDB)可能因后端连接问题返回失败,此时 fallback 到execute_sql执行SHOW PROCESSLIST。
list_history_connections
查询历史连接快照。需实例已开启会话快照采集。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
start_time | int | 是 | Unix 时间戳,快照搜索起始 | |
end_time | int | 是 | Unix 时间戳,快照搜索结束 | |
instance_id | str | 否 | 环境变量 | |
snapshot_time | int | 否 | 范围内最新 | 指定查询某个快照时间点 |
show_sleep | bool | 否 | False | True 时包含 Sleep 连接 |
sort_by | str | 否 | "time" | 排序字段:time / user / db / command |
page_number | int | 否 | 1 | |
page_size | int | 否 | 50 |
返回 data:
total: 连接总数connections[]:process_id,user,host,db,command,time,state,info,node_id,blocking_pidsnapshot_time: 实际查询的快照时间点snapshots_available: 该时间范围内可用快照数summary: 聚合统计by_user: 按用户 Top 10by_db: 按数据库 Top 10by_command: 按命令类型 Top 10
⚠️ 未开启会话快照时返回错误提示,需在控制台 → 数据库工作台 → 可观测性 → 会话 → 开启会话快照。
---
kill_process
终止进程。⚠️ 危险操作,需用户确认。支持两种模式:
模式 1(精确终止):传 process_ids + node_id,直接终止指定进程。
模式 2(按条件终止):传 kill_all=True 或筛选条件,内部通过 list_connections 服务端筛选获取匹配会话后批量终止。自动处理多节点分组。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
process_ids | list[str] | 模式1必填 | None | 进程 ID 列表 |
node_id | str | 模式1必填 | None | 节点 ID |
instance_id | str | 否 | 环境变量 | |
shard_id | str | 否 | None | 分片 ID(MongoDB) |
kill_all | bool | 否 | False | 终止全部会话 |
users | str \ | list[str] | 否 | None |
hosts | str \ | list[str] | 否 | None |
dbs | str \ | list[str] | 否 | None |
command_type | str | 否 | None | 按命令类型筛选(Query/Sleep 等) |
min_time | int | 否 | None | 最小执行时间(秒) |
sql | str | 否 | None | 按 SQL 语句匹配 |
describe_instance_nodes
查询实例节点列表。describe_instance_nodes(client, instance_id=None)
用于获取 node_id(部分函数如 VeDB 的 describe_deadlock 需要)。
返回 data.nodes[]:每个节点含 node_id、node_type(Primary/Secondary)、cpu_num、mem_in_gi_b、zone_id。
---
锁与事务
describe_deadlock
触发死锁分析并返回结果。自动查询 Primary 节点执行分析。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
instance_id | str | 否 | 环境变量 | |
page_number | int | 否 | 1 | |
page_size | int | 否 | 50 | 上限 100 |
返回 data:
total: 记录数items[]: 死锁详情diagnosis_time: 分析时间点node_id: Primary 节点 ID
describe_trx_and_locks
触发事务和锁分析并返回结果。自动查询 Primary 节点执行分析。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
instance_id | str | 否 | 环境变量 | |
page_number | int | 否 | 1 | |
page_size | int | 否 | 50 | 上限 100 |
lock_status | str | 否 | None | 锁状态过滤:"LockHold"(仅持锁)/ "LockWait"(仅等锁)/ "LockHoldAndWait" |
trx_status | str | 否 | None | 事务状态过滤:"RUNNING" / "LOCKWAIT" / "ROLLING_BACK" / "COMMITTING" |
process_id | str | 否 | None | 按进程 ID 精确匹配 |
trx_id | str | 否 | None | 按事务 ID 精确匹配 |
block_trx_id | str | 否 | None | 按阻塞事务 ID 精确匹配 |
快速定位持锁者:lock_status="LockHold" 可直接过滤出持有锁的事务。返回 data:
total: 记录数items[]: 事务和锁详情(trx_id,trxstatus,lockstatus,process_id,trx_exec_time,sql_blocked,lock_list/lock_summary,trx_rows_locked,block_trx_id等)diagnosis_time: 分析时间点node_id: Primary 节点 ID
lock_list超过 5 条时自动裁剪:保留前 5 条 +lock_summary(按类型/模式/状态聚合计数)+lock_count(总数)。
describe_lock_wait
触发锁等待分析并返回结果。自动查询 Primary 节点执行分析。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
instance_id | str | 否 | 环境变量 | |
page_number | int | 否 | 1 | |
page_size | int | 否 | 50 | 上限 100 |
r_trx_state | str | 否 | None | 等待方(被阻塞)事务状态:"RUNNING" / "LOCKWAIT" / "ROLLING_BACK" / "COMMITTING" |
b_trx_state | str | 否 | None | 阻塞方事务状态(同上) |
r_trx_id | str | 否 | None | 等待方事务 ID(精确匹配) |
b_trx_id | str | 否 | None | 阻塞方事务 ID(精确匹配) |
返回 data:
total: 记录数items[]: 锁等待详情(r_trx_id,r_waiting_query,r_trx_state,b_trx_id,b_blocking_query,b_trx_state,r_blocked_wait_secs,b_blocking_wait_secs等)diagnosis_time: 分析时间点node_id: Primary 节点 ID
---
日志与空间
describe_err_logs
查询错误日志。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
start_time | int | 是 | — | |
end_time | int | 是 | — | |
instance_id | str | 否 | 环境变量 | |
page_number | int | 否 | 1 | |
page_size | int | 否 | 10 | |
keyword | str | 否 | None | 关键字过滤 |
返回:data.err_logs[] — 错误日志列表,data.total — 总条数
describe_table_space
查询表空间详情。
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
client | ToolboxClient | 是 | — | create_client() 返回值 |
instance_id | str | 否 | 环境变量 | |
database | str | 否 | None | 过滤指定数据库 |
table_name | str | 否 | None | 过滤指定表 |
page_number | int | 否 | 1 | |
page_size | int | 否 | 10 |
开发测试 Skill
此 Skill 用于数据变更(DML)和结构变更(DDL),涉及工单审批流程。
依赖文件
scripts/dbw_client.py: HTTP 直连实现,仅依赖 Python 标准库scripts/toolbox.py: 封装create_client等函数
初始化工具箱
from toolbox import create_client
client = create_client()可用方法
1. 直接执行 SQL(可能被拦截)
execute_sql(client, sql="UPDATE users SET status=1 WHERE id=100")- 返回:
{"success": true, "data": {"state": "success", "row_count": N}} - 注意: 如果被安全规则拦截,会返回错误,此时需要创建工单
2. 创建 DML 工单(数据变更)
📖 指南:
- MySQL DML 指南
- PostgreSQL DML 指南
create_dml_sql_change_ticket(client,
sql_text="UPDATE users SET status=1 WHERE id=100",
title="更新用户状态",
memo="测试数据更新"
)- 返回:
{"success": true, "data": {"ticket_id": "...", "status": "TicketExamine", "ticket_url": "..."}}
3. 创建 DDL 工单(结构变更)
📖 指南:
- MySQL DDL 指南
- PostgreSQL DDL 指南
create_ddl_sql_change_ticket(client,
sql_text="ALTER TABLE users ADD COLUMN email VARCHAR(100)",
title="添加邮箱字段"
)- 返回: 同 DML 工单
4. 查询工单列表
describe_tickets(client, list_type="CreatedByMe")- 返回:
{"success": true, "data": {"tickets": [...]}}
5. 查询工单详情
describe_ticket_detail(client, ticket_id="ticket_xxx")- 返回:
{"success": true, "data": {"status": "...", "result": "..."}}
6. 查询审批流程
describe_workflow(client, ticket_id="ticket_xxx")- 返回:
{"success": true, "data": {"nodes": [...]}}
工作流
1. 尝试执行: 先尝试直接执行 execute_sql。 2. 创建工单: 如果被安全规则拦截(或属于高风险操作),则调用 create_dml_sql_change_ticket 或 create_ddl_sql_change_ticket 创建工单。 3. 等待审批: 创建工单后,状态通常为 TicketExamine(审批中)。
- 关键步骤: 必须明确告知用户工单需要审批。
- 行动: 提供审批人信息(从返回结果中获取
approver)和工单链接(ticket_url),并提示用户:"请联系审批人 [姓名] 进行审批,审批链接:[URL]"。
4. 等待执行: 审批通过后,工单进入 TicketWaitExecute(等待执行)状态;工单执行方式固定为 Manual(审批后手动执行),必须由用户在 DBW 控制台手动触发执行,不会自动跑 SQL。告知用户:"审批通过后,请到工单链接中点击「执行」按钮触发变更。" 5. 查询状态: 使用 describe_ticket_detail 查询工单最终执行状态。
MySQL DDL 结构变更指南
本指南基于公司内部《MySQL开发最佳实践》编写,详细说明如何编写符合规范的表结构变更(DDL)SQL 语句。
适用场景
- CREATE TABLE: 建表
- ALTER TABLE: 加减列、修改索引、修改表属性
- DROP TABLE/INDEX: 删表、删索引
核心规范
1. 存储引擎: 必须使用 InnoDB (或 MyRocks)。 2. 字符集: 统一使用 utf8mb4 (Collation: utf8mb4_0900_ai_ci 或 utf8mb4_bin)。 3. 主键强制: 必须有主键。推荐使用 `bigint unsigned` 类型,值由公司 ID Generator 生成(禁止使用数据库自增 ID)。 4. 禁止项: 严禁使用 Foreign Keys (外键), Triggers (触发器), Stored Procedures (存储过程), Views (视图), ENUM 类型。 5. 字段约束:
- 所有字段(除唯一索引/deleted_at)必须为
NOT NULL。 - 必须提供默认值(字符串
'', 数值0)。 - 必须有清晰的
COMMENT。
编写规范
1. 建表规范 (CREATE TABLE)
CREATE TABLE `user_orders` (
`id` bigint(20) unsigned NOT NULL COMMENT '主键ID', -- 不使用 AUTO_INCREMENT
`user_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
`order_no` varchar(64) NOT NULL DEFAULT '' COMMENT '订单号',
`amount` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额', -- 禁止使用 float/double
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态: 0-待支付, 1-已支付',
`description` varchar(2048) NOT NULL DEFAULT '' COMMENT '描述', -- 超过2048建议拆表
`is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '软删除标识',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 禁止使用 TIMESTAMP
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_status` (`user_id`, `status`) -- 联合索引遵循最左前缀
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户订单表';2. 修改表结构 (ALTER TABLE)
优先使用 Online DDL 特性,建议显式指定 ALGORITHM 和 LOCK。
-- 添加字段 (INSTANT 算法,MySQL 8.0+)
ALTER TABLE `user_orders`
ADD COLUMN `pay_channel` varchar(32) NOT NULL DEFAULT '' COMMENT '支付渠道',
ALGORITHM=INSTANT;
-- 添加索引 (INPLACE 算法,不阻塞 DML)
ALTER TABLE `user_orders`
ADD INDEX `idx_create_time` (`create_time`),
ALGORITHM=INPLACE, LOCK=NONE;最佳实践
字段设计
1. 类型选择:
- 整数:
tinyint(状态),bigint unsigned(ID). 禁止int做主键。 - 小数: 必须
decimal。 - 字符串:
char(定长),varchar(变长, <2048). 避免BLOB/TEXT(会导致临时表性能下降)。 - 时间:
datetime。
2. 冗余字段: 允许适当冗余以减少 Join,但需保证一致性。
索引设计
1. 命名: pk_ (主键), uk_ (唯一), idx_ (普通)。 2. 数量: 单表索引建议 < 5 个。 3. 最左前缀: 联合索引要把最常查询的列放在最左边。 4. 覆盖索引: 尽量让索引包含查询所需字段,减少回表。 5. 低基数列: 区分度低(如性别)的列不适合单独建索引。
变更风险控制
1. 大表变更:
- LOCK=NONE: 确保使用非锁表方式。
- 业务低峰: 在流量低谷期执行。
- 超时: 单条 DDL 执行预计超过 1分钟 需在工单备注说明。
2. 兼容性:
- 禁止 DROP 列: 除非确认代码已完全移除引用。
- 禁止修改列类型: 尤其是缩短长度或改变类型,风险极高。
工单填写建议
1. SQL 内容: 包含完整的 ALGORITHM 和 LOCK 提示(如果需要)。 2. 备注:
- 表数据量: 必须注明(如 "约 500万行")。
- 锁表风险: 评估是否会锁表。
- 业务影响: 是否需要停机或有短暂报错。
调用示例
# 创建 DDL 工单
create_ddl_sql_change_ticket(client,
sql_text="ALTER TABLE user_orders ADD INDEX idx_create_time (create_time), ALGORITHM=INPLACE, LOCK=NONE;",
title="订单表添加时间索引",
memo="优化后台查询性能,表数据量约 50万,使用 Online DDL 不锁表",
)MySQL DML 数据变更指南
本指南基于公司内部《MySQL开发最佳实践》编写,详细说明如何编写高质量、安全的数据变更(DML)SQL 语句。
适用场景
- INSERT: 插入新数据
- UPDATE: 更新现有数据
- DELETE: 删除数据
核心规范
1. 禁止无条件变更: UPDATE and DELETE 语句必须包含 WHERE 条件。 2. 批量限制: 单次 DML 操作数据量严禁超过 1000 条,建议控制在 100 条以内。超过此数量必须分批处理。 3. 禁止物理删除: 推荐使用软删除(如 update_time + deleted_at),避免直接 DELETE 数据。 4. 事务控制: 超过 60秒 的长事务必须拆分为多个小事务。
编写规范
1. INSERT 语句
建议明确指定列名,避免因表结构变更导致插入错误。
-- ✅ 推荐:指定列名
INSERT INTO users (id, username, email, status, create_time, update_time)
VALUES (123456789, 'zhangsan', 'zhangsan@example.com', 1, NOW(), NOW());
-- ✅ 推荐:批量插入(单批次 < 1000)
INSERT INTO users (id, username, status, create_time, update_time)
VALUES
(123456790, 'lisi', 1, NOW(), NOW()),
(123456791, 'wangwu', 1, NOW(), NOW());2. UPDATE 语句
必须包含 WHERE 子句。更新数据时,必须同时更新 update_time 字段。
-- ✅ 推荐:通过主键更新,并更新时间
UPDATE users
SET status = 2, update_time = NOW()
WHERE id = 123456789;
-- ✅ 推荐:带 Limit 限制(防止意外大规模更新)
UPDATE users
SET status = 2, update_time = NOW()
WHERE status = 1 AND create_time < '2023-01-01'
LIMIT 100;
-- ❌ 严禁:无条件更新
UPDATE users SET status = 0;3. DELETE 语句
严禁物理删除核心业务数据。应使用软删除字段(如 deleted_at 或 status)。
-- ✅ 推荐:软删除
UPDATE users
SET deleted_at = NOW(), update_time = NOW()
WHERE id = 123456789;
-- ⚠️ 慎用:物理删除(仅限日志/临时表清理)
-- 必须带 WHERE 和 LIMIT
DELETE FROM temp_logs
WHERE create_time < '2023-01-01'
LIMIT 100;最佳实践
安全第一
1. 先查后改 (Check before Act):
- 数据订正前,必须先执行
SELECT确认影响行数和数据内容。 - 示例:
-- 1. 确认
SELECT count(*) FROM users WHERE status = 1 AND create_time < '2023-01-01';
-- 2. 备份(对于重要数据,先导出或插入备份表)
-- 3. 执行更新
UPDATE users SET status = 2, update_time = NOW() WHERE status = 1 AND create_time < '2023-01-01' LIMIT 100;2. 避免长事务:
- 不要在事务中进行耗时操作(如调用外部接口)。
- 大批量数据变更脚本应在代码层面控制分批提交。
性能优化
1. 只更新必要字段: 不要全字段更新,减少 Binlog 量。 2. 索引利用: 确保 WHERE 子句中的条件字段有索引支持(最好是 ref 或 const 级别),避免全表扫描。 3. 主键顺序: 插入数据尽量按主键顺序,减少页分裂(如果使用 ID Generator 需注意 ID 的有序性)。
工单填写建议
1. SQL 内容:
- 多条 SQL 请用分号
;分隔。 - 单条 SQL 影响行数不应过大。
2. 备注 (Memo):
- 注明 变更原因、预估影响行数。
- 如果是大批量变更,请说明 分批策略。
- 注明 回滚方案。
调用示例
# 创建 DML 工单
create_dml_sql_change_ticket(client,
sql_text="UPDATE users SET status=2, update_time=NOW() WHERE id=123456789;",
title="修复用户状态异常",
memo="影响行数:1行,已Select确认。原因:工单 #5678",
)PostgreSQL DDL 结构变更指南
本指南详细说明如何编写符合规范的 PostgreSQL 表结构变更(DDL)SQL 语句。
适用场景
- CREATE TABLE: 建表
- ALTER TABLE: 加减列、修改索引、修改表属性
- DROP TABLE/INDEX: 删表、删索引
核心规范
1. 命名规范: 使用小写字母和下划线(snake_case)。 2. 主键强制: 必须有主键 (PRIMARY KEY)。 3. 注释: 表和关键字段必须有 COMMENT。 4. 类型推荐:
- ID:
bigserial(自增) 或uuid. - 时间:
timestamptz(带时区的时间戳). - JSON:
jsonb(二进制 JSON,支持索引). - 文本:
text(PG 中text和varchar性能几乎无差,推荐text).
编写规范
1. 建表规范 (CREATE TABLE)
CREATE TABLE user_orders (
id bigserial PRIMARY KEY, -- 自增主键
user_id bigint NOT NULL,
order_no text NOT NULL DEFAULT '',
amount numeric(12, 2) NOT NULL DEFAULT 0.00,
status smallint NOT NULL DEFAULT 0,
extra_info jsonb DEFAULT '{}', -- JSONB 类型
created_at timestamptz NOT NULL DEFAULT NOW(), -- 带时区
updated_at timestamptz NOT NULL DEFAULT NOW(),
deleted_at timestamptz
);
-- 添加注释
COMMENT ON TABLE user_orders IS '用户订单表';
COMMENT ON COLUMN user_orders.id IS '主键ID';
COMMENT ON COLUMN user_orders.status IS '状态: 0-待支付, 1-已支付';
-- 创建索引
CREATE UNIQUE INDEX uk_order_no ON user_orders (order_no);
CREATE INDEX idx_user_status ON user_orders (user_id, status);
-- GIN 索引用于 JSONB
CREATE INDEX idx_extra_info ON user_orders USING GIN (extra_info);2. 修改表结构 (ALTER TABLE)
PostgreSQL 的 DDL 大多支持事务,且很多操作(如添加带默认值的列)在 PG 11+ 是瞬间完成的。
-- 添加字段
ALTER TABLE user_orders
ADD COLUMN pay_channel text DEFAULT '';
-- 添加索引 (建议使用 CONCURRENTLY 避免锁表)
-- 注意: CONCURRENTLY 不能在事务块中运行,但在工单系统中通常会单独处理
CREATE INDEX CONCURRENTLY idx_created_at ON user_orders (created_at);
-- 修改字段类型 (慎用,可能会重写表)
-- 这种操作建议在业务低峰期进行
ALTER TABLE user_orders
ALTER COLUMN amount TYPE numeric(14, 2);最佳实践
锁机制与风险
1. CREATE INDEX CONCURRENTLY:
- 标准
CREATE INDEX会锁定表(排他锁),阻止写入。 - 强烈推荐使用
CONCURRENTLY关键字创建索引,它允许在构建索引时继续进行读写操作。 - 注意:
CONCURRENTLY操作耗时更长,且不能在事务块中执行(工单系统需特殊支持或单独执行)。
2. 添加列:
- PG 11+ 添加带
DEFAULT值的列不需要重写表,非常快。
3. 锁队列: DDL 操作需要获取排他锁,如果长事务占用了锁,DDL 会等待,导致后续的所有查询也被阻塞(锁队列效应)。务必确保没有长事务运行。
工单填写建议
1. 索引创建: 显式注明是否使用 CONCURRENTLY。 2. 备注:
- 表数据量: 必须注明。
- 业务影响: 是否需要停机。
调用示例
# 创建 DDL 工单
create_ddl_sql_change_ticket(client,
sql_text="CREATE INDEX CONCURRENTLY idx_created_at ON user_orders (created_at);",
title="订单表添加时间索引",
memo="PG 库,使用 CONCURRENTLY 避免锁表,表数据量约 100万",
)PostgreSQL DML 数据变更指南
本指南详细说明如何编写高质量、安全的 PostgreSQL 数据变更(DML)SQL 语句。
适用场景
- INSERT: 插入新数据
- UPDATE: 更新现有数据
- DELETE: 删除数据
核心规范
1. 禁止无条件变更: UPDATE and DELETE 语句必须包含 WHERE 条件。 2. 批量限制: 建议单次操作数据量控制在合理范围,大批量更新建议分批进行以减少 WAL 日志压力和锁竞争。 3. 禁止物理删除: 推荐使用软删除(如 deleted_at),避免直接 DELETE 数据。 4. 事务控制: 长时间运行的事务会阻碍 VACUUM 清理旧版本数据,应避免长事务。
编写规范
1. INSERT 语句
建议明确指定列名,推荐使用 RETURNING 子句获取插入后的数据(如自增ID)。
-- ✅ 推荐:指定列名
INSERT INTO users (username, email, status, created_at, updated_at)
VALUES ('zhangsan', 'zhangsan@example.com', 1, NOW(), NOW());
-- ✅ 推荐:批量插入
INSERT INTO users (username, status)
VALUES
('lisi', 1),
('wangwu', 1);
-- ✅ 推荐:插入并返回 ID
INSERT INTO users (username, email)
VALUES ('zhaoliu', 'zhaoliu@example.com')
RETURNING id;
-- ✅ 推荐:Upsert (插入或更新)
INSERT INTO users (id, username, email)
VALUES (1, 'zhangsan', 'new_email@example.com')
ON CONFLICT (id)
DO UPDATE SET email = EXCLUDED.email, updated_at = NOW();2. UPDATE 语句
必须包含 WHERE 子句。
-- ✅ 推荐:通过主键更新
UPDATE users
SET status = 2, updated_at = NOW()
WHERE id = 1001;
-- ✅ 推荐:使用 RETURNING 获取更新后的值
UPDATE users
SET status = 2
WHERE id = 1001
RETURNING id, status, updated_at;3. DELETE 语句
推荐使用软删除。
-- ✅ 推荐:软删除
UPDATE users
SET deleted_at = NOW(), updated_at = NOW()
WHERE id = 1001;
-- ⚠️ 慎用:物理删除
DELETE FROM temp_logs
WHERE created_at < '2023-01-01';最佳实践
安全与性能
1. VACUUM 友好: 频繁的 UPDATE 和 DELETE 会产生死元组(Dead Tuples)。确保表上有合理的 autovacuum 设置。 2. 索引利用: 确保 WHERE 条件命中索引。 3. JSONB 更新: 更新 JSONB 字段时,尽量只更新需要的键值,或者使用 jsonb_set。
工单填写建议
1. SQL 内容: 使用标准 SQL 语法。 2. 备注:
- 注明 变更原因。
- 如果是大批量变更,请说明 分批策略。
调用示例
# 创建 DML 工单
create_dml_sql_change_ticket(client,
sql_text="UPDATE users SET status=2, updated_at=NOW() WHERE id=1001;",
title="修复用户状态",
memo="PG 库数据修复",
)全域资产盘点 (Global Asset Inventory)
全域资产盘点是数据治理的第一步,旨在摸清“家底”,建立完整、准确的数据资产清单。通过自动化扫描,可以发现未登记的“影子资产”,监控资产规模变化。
核心目标
1. 资产发现:自动发现所有数据库实例、数据库和表。 2. 变更监控:识别新增或删除的资产。 3. 资产统计:统计各业务线的存储量、表数量等指标。 4. 影子资产治理:找出未在 CMDB 或元数据中心登记的数据库。
盘点流程
1. 实例层盘点
首先获取云账号下所有的数据库实例信息。
from toolbox import create_client
client = create_client()
# 获取所有实例(支持分页)
instances = []
page = 1
while True:
res = list_instances(client, page_number=page, page_size=50)
if not res['success']:
break
data = res['data'].get('instances', [])
if not data:
break
instances.extend(data)
page += 1
print(f"共发现 {len(instances)} 个数据库实例")关注指标:
- 实例 ID (
instance_id) - 实例名称 (
instance_name) - 实例类型 (
instance_type): MySQL, Postgres, Redis 等 - 运行状态 (
status): Running, Stopped 等 - 地域信息 (
region)
2. 数据库层盘点
对每个运行中的实例,获取其包含的逻辑数据库列表。
for inst in instances:
if inst['status'] != 'Running':
continue
print(f"正在扫描实例: {inst['name']} ({inst['id']})")
# 获取该实例下的数据库
db_res = list_databases(client, instance_id=inst['id'], page_size=100)
if db_res['success']:
dbs = db_res['data'].get('databases', [])
print(f" - 发现 {len(dbs)} 个数据库")
# TODO: 存入资产清单数据库关注指标:
- 数据库名 (
db_name) - 字符集 (
charset) - 创建时间
3. 表级资产盘点
深入到每个数据库,获取表清单。这是构建数据地图的基础。
# 伪代码示例
for db in dbs:
table_res = list_tables(client,
instance_id=inst['id'],
database=db['name'],
page_size=100
)
if table_res['success']:
tables = table_res['data'].get('tables', [])
print(f" - 库 {db['name']} 包含 {len(tables)} 张表")盘点产出物
建议将盘点结果存储为结构化数据(如 Excel 或 数据库表),包含以下字段:
| 字段名 | 描述 | 示例 |
|---|---|---|
asset_id | 资产唯一标识 | inst-xxx.db_name.table_name |
instance_name | 实例名称 | prod-main-db |
db_name | 数据库名称 | order_center |
table_name | 表名称 | t_order_main |
owner | 负责人 | zhangsan |
last_check_time | 最后盘点时间 | 2024-03-20 10:00:00 |
status | 状态 | Active / Deleted |
常见问题
- Q: 如何处理海量表的扫描?
- A: 建议使用多线程或异步任务队列(Celery)并发扫描不同的实例。
- Q: 盘点频率多少合适?
- A: 建议每日凌晨执行一次全量盘点,或者针对生产环境进行高频(每小时)的变更检测。
数据探查 (Data Profiling)
数据探查是对现有数据进行统计分析和结构化审查的过程。通过自动化探查,可以快速了解数据的分布特征、值域范围和潜在问题,为数据清洗和建模提供依据。
探查目标
1. 数据分布分析:了解数值型字段的极值、均值,枚举型字段的分布。 2. 结构一致性:验证数据是否符合预期的格式(如日期格式、邮箱格式)。 3. 异常发现:识别离群值(Outliers)和不符合业务逻辑的数据。
核心指标
| 指标类型 | 具体指标 | 适用字段类型 | 业务含义 |
|---|---|---|---|
| 基础统计 | 行数 (Count), 空值率 (Null%) | 所有 | 数据完整性概览 |
| 基数分析 | 唯一值数 (Cardinality), 唯一值率 | 字符串, ID | 识别主键或枚举字段 |
| 数值分布 | Min, Max, Avg, Median, StdDev | 数值型 | 了解业务规模和波动 |
| 枚举分布 | Top N 值及占比 | 类别型 | 了解业务构成(如各城市订单占比) |
| 长度分析 | 最大/最小/平均长度 | 字符串 | 优化存储空间定义 |
自动化探查流程
1. 单表探查 (Single Table Profiling)
针对单张表,生成全字段的探查报告。
from toolbox import create_client
import pandas as pd
client = create_client()
def profile_table(instance_id, db, table):
# 1. 获取表结构
schema = get_table_info(client, instance_id=instance_id, database=db, table=table)
if not schema['success']:
return
columns = schema['data']['columns']
# 2. 构建探查 SQL
# 为了性能,建议使用采样或分批查询,这里演示全量聚合
selects = ["COUNT(*) as total_rows"]
for col in columns:
col_name = col['name']
col_type = col['type'].lower()
# 基础统计:空值数
selects.append(f"COUNT(CASE WHEN {col_name} IS NULL THEN 1 END) as {col_name}_nulls")
# 基数统计:唯一值数
selects.append(f"COUNT(DISTINCT {col_name}) as {col_name}_distinct")
# 数值型特定统计
if any(t in col_type for t in ['int', 'decimal', 'float', 'double']):
selects.append(f"MAX({col_name}) as {col_name}_max")
selects.append(f"MIN({col_name}) as {col_name}_min")
selects.append(f"AVG({col_name}) as {col_name}_avg")
sql = f"SELECT {', '.join(selects)} FROM {table}"
# 3. 执行查询
res = execute_sql(client, sql=sql, instance_id=instance_id, database=db)
if res['success']:
return res['data']['rows'][0]2. 深度探查 (Deep Profiling)
对于关键字段,进行更深入的频率分布分析。
示例:统计用户所在城市分布
sql = """
SELECT city, COUNT(*) as cnt, COUNT(*)*100.0/(SELECT COUNT(*) FROM users) as pct
FROM users
GROUP BY city
ORDER BY cnt DESC
LIMIT 20
"""
res = execute_sql(client, sql=sql, instance_id="inst-xxx", database="user_db")探查报告应用
1. 元数据增强:将探查出的“最大长度”、“枚举值列表”回写到元数据管理系统。 2. 开发辅助:开发人员在写 SQL 前,先查看探查报告,避免因数据倾斜导致的性能问题。 3. 存储优化:发现定义了 VARCHAR(1000) 但实际最大长度仅 20 的字段,建议缩减。
Related skills
FAQ
Which databases are supported?
Volcengine MySQL, veDB-MySQL, PostgreSQL, SQL Server, MongoDB, Redis, plus self-built MySQL and PostgreSQL; ByteCloud databases are not supported.
Can it execute write SQL?
No. execute_sql is read-only; INSERT/UPDATE/DELETE/DDL must go through ticket functions per the safety rules.