想象你所在的整座城市有一个巨大的档案馆,里面存放着建城以来每一笔金融交易的原始凭证。要查某个人三年前的一笔转账,管理员只能抱着几十本厚账本从头翻起,而且每天闭馆时还会运来几车新账本——这就是区块链世界的原始体验:数据全都在,但想快速精准地找到某一条,难如大海捞针。The Graph 正是给这座档案馆修建的“现代图书馆检索系统”:专业的“图书管理员”提前把账本里的关键信息整理成一张张主题索引卡片,任何人来查询,只需递上一张检索单,几秒钟就能拿到答案。这篇文章就用“图书馆”贯穿全程,带你认识 The Graph 的协议架构、子图开发、GraphQL 查询与 GRT 通证激励。

岗位关联

掌握 The Graph,等于掌握了 Web3 开发者面对链上数据时最趁手的“检索技能”,无论做 DApp 前端、链上数据分析还是 DeFi 风控,你都能比同行更快拿到干净、规范的数据。

目录
  1. 可视化演示
  2. 索引之困
  3. 三角色架构
  4. 子图定义
  5. 开发四步
  6. GraphQL查询
  7. GRT激励
  8. 实战落地
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示区块链数据索引管线、子图事件映射流程,以及去中心化索引市场的GRT质押激励机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

索引管线架构 链上数据:区块事件实时监听 索引节点:提取并转换链上数据 Query→GraphQL→Subgraph 索引节点将链上数据转为可查询格式

索引之困

想象你走进一座没有目录的档案馆:账本堆满整面墙,管理员找一笔三年前的转账,只能从第一本账本翻起,翻完 20 万页才发现记错了日期。更崩溃的是,每天闭馆时又会运来几车新账本。这种“全部数据都在,却无从下手”的状态,就是没有索引的区块链体验。

区块链上的全节点确实保存了自创世以来的每一笔数据,但要通过 JSON-RPC 接口“按条件查找”时,能力非常有限。比如想知道某个地址的历史转账,最常见的做法是从第一个区块开始逐个遍历,把每笔交易拉下来比对;想过滤某种类型的日志,还要手工构造主题过滤器并解析十六进制编码的日志。换个场景,如果想知道某个 DeFi 协议所有池子当前锁定了多少美元,靠一个一个区块翻出来再累加,几乎不可行,而且大部分公共 RPC 节点还会限流。

The Graph 解决的正是“检索效率”问题:它把链上事件预先解析、清洗、建模后存入数据库,对外提供索引接口。查询的复杂度从“全表扫描”降到“按索引查找”,效率提升可以用下面这个比值描述:

回到图书馆的比喻:管理员花大力气把账本整理成索引卡片,读者以后查资料就不用再从第一页翻起。The Graph 的价值,就是让“找数据”从体力活变成脑力活。

η=TscanTindex=nlog⁡2n\displaystyle \eta = \frac{T_{\text{scan}}}{T_{\text{index}}} = \frac{n}{\log_2 n}
  1. 分子 n全链扫描需要依次检查的区块或交易数量,查询耗时与数据总量成正比。
    nn
  2. 分母 log₂n在已建立的索引上做二分或树形查找,最多需要比较的次数,增长速度远低于 n。
    log⁡2n\log_2 n
  3. 比值 η索引查询相对全链扫描的效率提升倍数。区块越多,η 越大,节省的时间越惊人。
    nlog⁡2n\frac{n}{\log_2 n}

三角色架构

一座图书馆要高效运转,不能只有一张服务台,还得有整理书籍的整理员、决定哪些书值得做专题索引的选书人、以及把书借给整理员并分享收益的市民。The Graph 网络也对应着三个核心角色:索引者(Indexer)、策展人(Curator)和委托人(Delegator),再加上发起查询的消费者(Consumer)。

索引者就是“图书管理员”,他们质押 GRT 通证运行索引节点,负责处理子图数据,并提供 GraphQL 查询服务;查询质量越高、响应越快,越能吸引消费者。策展人是“选书人”,他们发现某个子图有价值,就质押 GRT 对它发出“信号”,引导索引者优先为这个主题投入算力。委托人不想亲自运营节点,但想参与生态收益,便把自己的 GRT 委托给信任的索引者,按比例分享未来收益。消费者(通常是 DApp 或链上数据分析工具)发起查询,并支付对应的查询费。

三个角色的质押形成了一套权重体系。网络在分配查询工作量或收益时,会按质押占比计算权重:

图书馆里的人各司其职:整理员出力,选书人把关,市民出书,读者受益。The Graph 用通证把三个角色的利益绑在一起,形成一条互相促进的“查书流水线”。

Wi=Si∑jSj\displaystyle W_i = \frac{S_i}{\sum_{j} S_j}
  1. S_i索引者 i 所质押的 GRT 数量,代表他投入的资源与承诺。
    SiS_i
  2. 分母 ΣS_j所有参与网络质押的 GRT 总量,包括全部索引者的质押。
    ∑jSj\sum_{j} S_j
  3. 权重 W_i索引者 i 在查询分配或收益中占的比例。质押越多,信誉越高,获得的查询流量也越多。
    Wi=Si∑jSjW_i = \frac{S_i}{\sum_{j} S_j}

子图定义

图书馆里最值钱的东西,是那些“专题索引卡片”:比如“按作者姓名的索引”“按出版年份的索引”。The Graph 里这样的专题索引叫子图(Subgraph)。它不是把整条链复制一份,而是只针对某个协议或业务,抽取相关事件并建模成便于查询的实体。

一张专题索引卡片要能工作,需要三种信息:卡片版式、资料来源、摘抄规则。对应到子图就是三个文件。首先是 manifest(subgraph.yaml),它声明数据源——合约地址、网络、要监听哪些事件;然后是 schema.graphql,它定义实体模型,相当于卡片上有哪些栏目;最后是 mappings,由 AssemblyScript 编写,负责把链上事件“翻译”成对实体的增删改操作。

整个子图的核心逻辑,就是这样一个映射过程:

子图就像选书人亲手做的专题卡片集:不搬走整座档案馆,只摘录大家最常问的信息,并且持续跟随新账本更新,让“专题检索”又快又准。

fmap:Eventchain→Entitygraph\displaystyle f_{\text{map}}: \text{Event}_{\text{chain}} \to \text{Entity}_{\text{graph}}
  1. Event_chain链上产生的一个原始事件,比如一次转账或一次兑换,包含 topics 和 data 等原始字段。
    Eventchain\text{Event}_{\text{chain}}
  2. Entity_graph经过建模后的实体对象,例如用户、交易对、订单,拥有清晰的字段类型与关系。
    Entitygraph\text{Entity}_{\text{graph}}
  3. 映射函数 f_map由子图开发者编写的事件处理逻辑,决定事件如何被解码、清洗并写入索引数据库。
    fmapf_{\text{map}}

开发四步

写一张专题索引卡片也有固定工序:先领空白卡片模板,再填写卡片栏目,然后逐条摘抄账本内容,最后把卡片插入检索柜供读者使用。子图开发同样分四步。

第一步是初始化项目,使用 graph init 命令创建子图脚手架,自动生成项目目录和基础配置。第二步是编写配置与模型:在 subgraph.yaml 中填写合约地址与要监听的事件,在 schema.graphql 中定义实体和字段,随后运行代码生成器,把 schema 编译成 TypeScript 类型。第三步是编写映射逻辑,也就是在 mappings 中用 AssemblyScript 处理事件,把参数写到对应实体中。第四步是部署与验证,通过 graph deploy 上传子图,并在网关上执行一条测试查询确认数据正确。

随着新区块不断产生,索引也需要持续“追更”。这个过程可以看作一次并集更新:

就像图书馆每晚闭馆后都会把当天新到的账本摘录入卡片柜,子图也会在每个新区块出现后自动补充索引,读者第二天一早总能查到最新数据。

St+1=St∪ΔS(blockt+1)\displaystyle S_{t+1} = S_t \cup \Delta S(\text{block}_{t+1})
  1. S_t当前已经索引好的实体集合,即截至区块 t 的数据库快照。
    StS_t
  2. ΔS(block_{t+1})新产生的区块 t+1 中,被映射函数捕捉到的事件带来的实体变更。
    ΔS(blockt+1)\Delta S(\text{block}_{t+1})
  3. 并集更新新变更合并进已有集合,索引状态不断向前推进,与链上状态保持最终一致。
    St+1=St∪ΔS(blockt+1)S_{t+1} = S_t \cup \Delta S(\text{block}_{t+1})

GraphQL查询

读者在图书馆里不用自己翻书,只需填一张“检索单”:要查什么字段、按什么条件过滤、按什么排序。这张检索单的语言就是 GraphQL。它由 The Graph 对外提供,但你把它当成一种通用的“点菜式”查询语言就好:你要什么,服务员给你上什么,不多不少。

一个典型查询会声明所需字段,并通过参数控制返回范围。比如查询“储备量最高的五个交易对”,可以指定实体类型 pairs,用 orderBy 按储备金排序,用 orderDirection 指定降序,再用 first 限制条数。嵌套关系也能一步带出:查交易对的同时,把对应的两个代币符号取出来。分页则通过 first 与 skip 游标实现,适合前端滚动加载。

从数据库角度看,这类查询可以被理解为先筛选、再投影的关系运算:

对比原来的体验:以前要把账本整段搬回来自己拆解,现在只需递上一张检索单,管理员几秒钟就把整理好的答案送到你面前。这就是 GraphQL 查询的魅力。

R=πfields(  σwhere(Dgraph)  )\displaystyle R = \pi_{\text{fields}}(\;\sigma_{\text{where}}(D_{\text{graph}})\;)
  1. σ_where(D_graph)对索引数据库先做条件过滤,只保留满足 where 条件的记录。
    σwhere(Dgraph)\sigma_{\text{where}}(D_{\text{graph}})
  2. π_fields(……)对过滤结果做字段投影,只摘取查询里要求的字段列。
    πfields\pi_{\text{fields}}
  3. 结果 R最终返回给前端的数据结构,干净、嵌套清晰,省去大量手工解析。
    RR

GRT激励

图书馆要持续运转,需要电费、员工工资和新书采购费。The Graph 的“图书馆经费”由 GRT 通证承载,贯穿所有角色。索引者质押 GRT 才能获得服务资格;策展人质押 GRT 对子图发信号,引导资源流向优质主题;委托人把 GRT 委托给索引者,换取未来分成;消费者则在使用查询服务时支付 GRT。每一条链路都是实实在在的经济激励。

索引者的收益主要来自查询费分成。网络会按质押权重分配查询任务,质押越多、服务越好,分到的查询费就越高。把前面提到的权重公式代入收益计算,就是:

在这套机制里,策展人是“风向标”,委托人是“出资人”,索引者是“出力人”。谁的判断准、服务好,谁就能从图书馆的收入蛋糕中分到更大一块,生态也因此自我驱动、持续生长。

Ri=Si∑jSj×Fq\displaystyle R_i = \frac{S_i}{\sum_{j} S_j} \times F_q
  1. F_q一段时间内该索引者服务的子图产生的查询费总额。
    FqF_q
  2. 质押占比该索引者质押量占全网质押量的比例,决定他能分走查询费的份额。
    Si∑jSj\frac{S_i}{\sum_{j} S_j}
  3. 收益 R_i索引者最终获得的查询费收入,质押与服务越好,收入越高。
    RiR_i

实战落地

当这套“图书馆检索系统”建好之后,城市里到处都可以用:DApp 前端用它加载用户资产历史,数据分析平台用它聚合全协议的交易池数据,风控系统用它监听巨鲸异动。对 Web3 开发者来说,接入 The Graph 已经是常规操作。

实际工程中,性能与可靠性是绕不开的话题。一个请求的完整耗时可以简单拆成三段:

优化方向也由此而来:尽量让数据在后端过滤干净,避免一次拉取整张表;合理建模实体,减少不必要的连接查询;关注子图当前的索引区块高度,判断数据是否已经追到最新;部署时选择稳定的网关与索引者。把这几点做好,你的 DApp 就能享受到“图书馆检索系统”真正的快捷与稳定。

Ltotal=Lnetwork+Lindex+Lserialize\displaystyle L_{\text{total}} = L_{\text{network}} + L_{\text{index}} + L_{\text{serialize}}
  1. L_network请求在公网上的往返延迟,取决于网关节点与索引者节点的位置。
    LnetworkL_{\text{network}}
  2. L_index索引者节点执行 GraphQL 查询、命中数据库索引的时间。
    LindexL_{\text{index}}
  3. L_serialize结果序列化并返回给前端所花的时间,字段越多越大。
    LserializeL_{\text{serialize}}

AI 应用场景

DApp 前端数据接入

阶段:数据获取与特征前置

解决的问题:传统 DApp 需要前端重复调用 RPC,逐个解析日志、拼接逻辑,既慢又容易出错。子图把链上数据提前整理成结构化实体,前端一次 GraphQL 请求就能拿到全部所需字段,响应速度大幅提升。

具体案例:一个借贷类 DApp 需要展示用户的借还款历史、抵押率与利息累计。接入子图后,前端只需按用户地址查询“用户实体 + 借还款列表”,原本数百行 RPC 轮询与日志解码代码缩成了几十行查询语句。

链上数据聚合看板

阶段:数据清洗与聚合

解决的问题:分析平台要统计某协议所有流动性池的总锁仓量、交易量、手续费等指标,靠链上逐块扫描几乎不可能实时完成。子图天然支持跨实体的聚合与排序,能满足仪表盘的实时刷新需求。

具体案例:一个 DeFi 排名网站通过查询某交易平台的子图接口,按储备量倒序拉取 Top 20 交易对,结合代币价格计算协议级 TVL,并绘制趋势图。整个数据链路从“小时级”升级为“秒级”。

大额异动监控告警

阶段:实时事件监听

解决的问题:安全团队希望第一时间发现巨鲸转账、协议异常提款等事件。子图对转账事件做了实体化建模,配合过滤条件即可完成高吞吐量监听,无需自己部署索引服务。

具体案例:某风控平台利用子图监控目标地址的 Transfer 事件,当单笔金额超过阈值时触发 webhook 告警,并在看板上展示资金流向图。相比原生 RPC 轮询,误报率更低、延迟更短。

Python 演示

python45 行
import jsonimport urllib.request SUBGRAPH_URL = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2" QUERY = """{  pairs(orderBy: reserveUSD, orderDirection: desc, first: 5) {    id    token0 { symbol }    token1 { symbol }    reserveUSD  }}"""  def query_subgraph(endpoint, graphql_query):    request = urllib.request.Request(        endpoint,        data=json.dumps({"query": graphql_query}).encode("utf-8"),        headers={"Content-Type": "application/json"},    )    with urllib.request.urlopen(request, timeout=15) as response:        return json.loads(response.read().decode("utf-8"))  def main():    try:        result = query_subgraph(SUBGRAPH_URL, QUERY)    except urllib.error.URLError as exc:        raise SystemExit("网络请求失败: {}".format(exc))    if "errors" in result:        raise SystemExit("子图返回错误: {}".format(result["errors"]))    pairs = result["data"]["pairs"]    print("Uniswap V2 按储备量排序的 Top5 交易对:")    for pair in pairs:        symbol0 = pair["token0"]["symbol"]        symbol1 = pair["token1"]["symbol"]        reserve = float(pair["reserveUSD"])        print("  {}/{}: {:.0f} USD".format(symbol0, symbol1, reserve))  if __name__ == "__main__":    main()
目的:用 Python 调用 The Graph 公开子图接口,查询 Uniswap V2 中储备量最高的五个交易对。
  1. SUBGRAPH_URL = "..."子图托管端点,相当于图书馆“服务台地址”。
  2. QUERY = """..."""GraphQL 检索单,声明要查的交易对字段与排序方式。
  3. json.dumps({"query": graphql_query}).encode("utf-8")把查询包装成 JSON,编码为请求体。
  4. headers={"Content-Type": "application/json"}告诉服务端发送的是 JSON 数据。
  5. with urllib.request.urlopen(request, timeout=15) as response:发起网络请求并读取响应。
  6. if "errors" in result:检查子图是否返回了错误对象,有则中止。
  7. pairs = result["data"]["pairs"]从响应中取出交易对数组,开始逐条格式化输出。
  8. if __name__ == "__main__":程序入口,保证模块被导入时不会直接执行。

常见误区

误区我查子图接口,返回的数据应该就是区块链当前时刻的状态,可以用来做精确判断。

正解子图是异步索引的,新区块产生后需要时间被索引者抓取、处理、入库。这段延迟通常有几秒到几分钟。对时序敏感的场景,应该查询子图返回中的 _meta 字段里的 block.number,对比链上最新区块高度,判断数据是否已经“追平”。

为何常见:索引层查询太顺畅,容易让人误以为它跟全节点一样实时,忽略了事件处理流水线的存在。

误区子图查询结果既然来自链上事件,那它就可以作为合约之间的结算依据。

正解子图是链下派生数据,属于“最终一致”的索引视图,可能出现延迟、遗漏甚至开发者配置错误。任何涉及资金安全的高价值操作,最终都必须回到链上进行验证,子图更适合做展示、聚合与检索。

为何常见:查询结果长得太像“权威数据”,金融场景中容易放松对数据源的警戒。

误区要做子图,就得先搭一台全节点服务器,否则无数据可索引。

正解The Graph 的去中心化网络和托管服务都由专业索引者运行节点,子图开发者只需提供 subgraph.yaml、schema.graphql 和 mappings 三件套,部署后就能对外提供查询。自建节点只是很多索引者的内部选择,不是开发者的门槛。

为何常见:听说了“索引者运行节点”这件事,就以为子图开发也要自己运维基础设施。

误区The Graph 叫什么,GraphQL 就是它出的,离开链上数据就用不了。

正解GraphQL 是 Facebook 开源的一种通用 API 查询语言,广泛用于各类传统后端服务。The Graph 只是借助 GraphQL 来暴露链上索引数据,二者不是绑定关系。

为何常见:两者名字都带“Graph”,初学者很容易把协议和查询语言画上等号。

误区只要想查某个合约的数据,就必须先给这个合约开发一个专属子图。

正解简单的合约状态直接通过 RPC 读取即可;需要跨合约聚合、历史检索、复杂筛选时才适合引入子图。而且一个子图可以在 manifest 中声明多个数据源,同时索引一组相关合约,不必“一合约一子图”。

为何常见:误把子图当成了访问链上数据的唯一入口,忽略了它只是索引层的补充。

面试题

Q1. The Graph 网络的核心功能是什么?

单选题 · easy

  • A.发行去中心化稳定币
  • B.为链上数据建立索引← 正确答案
  • C.替代智能合约执行逻辑
  • D.提供共享矿机算力

答案:B。The Graph 的本质是区块链数据的索引协议,相当于给链上数据建图书馆检索系统。A 是 MakerDAO 类项目的事,C 是链上合约本身的事,D 与 The Graph 无关。

Q2. 子图的 schema.graphql 文件主要用来做什么?

单选题 · easy

  • A.定义实体与字段模型← 正确答案
  • B.设置节点的质押金额
  • C.存储链上原始日志
  • D.分配查询手续费

答案:A。schema.graphql 定义实体类型和字段,相当于专题索引卡片的“版式设计”。质押金额属于协议层,日志存储由索引者数据库负责,手续费分配属于经济层,都与 schema 无关。

Q3. 下列哪个角色在 The Graph 中负责运行节点并提供查询服务?

单选题 · easy

  • A.委托人(Delegator)
  • B.策展人(Curator)
  • C.索引者(Indexer)← 正确答案
  • D.验证者(Validator)

答案:C。索引者就是图书馆里的“整理员”,质押 GRT 运行索引节点并响应查询;策展人负责发信号筛选优质子图,委托人只出借 GRT 分享收益,The Graph 中不存在名为“验证者”的核心角色。

Q4. 一个完整的子图通常由哪三部分核心文件组成?

单选题 · mid

  • A.清单、模式、映射代码← 正确答案
  • B.合约、字节码、编译器
  • C.接口、事件、交易哈希
  • D.区块、交易、共识规则

答案:A。子图三件套是 manifest(数据源清单)、schema(实体模式)和 mappings(事件映射代码)。B 属于合约开发的产物,C 和 D 描述的是链上通用概念,不是子图的组成结构。

Q5. GraphQL 查询中的 orderBy 参数的主要作用是什么?

单选题 · mid

  • A.对结果按指定字段排序← 正确答案
  • B.过滤掉空值字段
  • C.限制返回的字段集合
  • D.合并多个数据源

答案:A。orderBy 指定按哪个字段排序,配合 orderDirection 控制升序或降序,类似“检索单上写按金额从高到低排列”。过滤空值、限制字段、合并数据源是其他参数或操作符的职责。

Q6. 关于 GRT 的质押与委托,下列说法正确的有哪些?

多选题 · mid

  • A.委托人可将 GRT 委托给索引者分享收益← 正确答案
  • B.策展人通过质押 GRT 标记优质子图← 正确答案
  • C.索引者无需质押即可提供查询服务
  • D.查询者必须质押 GRT 才能发起查询

答案:AB。索引者必须质押 GRT 才能参与服务,C 错;查询者只需支付查询费,无需质押,D 错。委托人出借 GRT 分享收益,策展人质押 GRT 发信号,A 和 B 正确。

Q7. 相比直接调用 RPC 读区块链,用子图查询有哪些优势?

多选题 · mid

  • A.支持聚合关联实体数据← 正确答案
  • B.免去手工解析原始日志← 正确答案
  • C.可以直接修改链上状态
  • D.支持复杂的过滤与排序← 正确答案

答案:ABD。子图把散乱的日志解析成实体模型,并支持跨实体聚合、过滤和排序,A、B、D 都对。索引层永远不能修改链上状态,链上状态只能由交易改变,C 错。

Q8. 下列哪些数据查询场景适合使用 The Graph 子图?

多选题 · hard

  • A.查询钱包的历史转账记录← 正确答案
  • B.聚合项目的流动性池数据← 正确答案
  • C.读取合约部署时的字节码
  • D.监听并统计巨鲸大额转账← 正确答案

答案:ABD。历史转账、流动性池聚合、大额转账统计都需要事件解析与聚合检索,正是子图擅长的场景。而合约字节码是部署时就确定的静态数据,用 RPC 直接读取即可,不需要建索引。

Q9. The Graph 去中心化网络与早期托管服务的区别在于?

单选题 · hard

  • A.去中心化网络由多索引者竞争服务← 正确答案
  • B.托管服务的查询结果完全不可信
  • C.去中心化网络不需要任何质押
  • D.托管服务能永久保存全部数据

答案:A。去中心化网络由众多索引者质押并竞争查询订单,而托管服务由官方集中维护。B 过于绝对,C 错误(索引者必须质押),D 与事实不符,托管服务也会随协议迁移淘汰旧子图。

Q10. 关于索引者(Indexer)收益来源,正确的描述是哪个?

单选题 · extreme

  • A.查询费分成与网络质押奖励← 正确答案
  • B.为子图发信号获取手续费
  • C.打包区块所获得的 Gas 收入
  • D.出售节点硬件获得的收入

答案:A。索引者通过服务查询获得查询费分成,在部分网络阶段还能获得质押奖励,A 正确。为子图发信号是策展人的行为,打包区块是矿工或验证人的收入,出售硬件与协议激励无关。