SurrealDB SurrealQL 查询语言详解

系统讲解 SurrealDB 3.3.0 查询语言 SurrealQL:语言总览与语法基础、九类核心语句逐类给出语法参数与真实回显、场景化查询模板、函数速查表与性能陷阱实测。

SurrealDBSurrealDB2026/10/17

SurrealDB SurrealQL 查询语言详解

‍‍​​‌​> 目标:系统讲解 SurrealDB 3.3.0 的查询语言 SurrealQL——语法基础、九类核心语句(DEFINE/CREATE/INSERT/SELECT/UPDATE/PATCH/RELATE/DELETE/事务控制)逐类给出「语法 → 参数 → 实测示例 + 真实回显」,并沉淀场景化查询模板、函数速查表与性能陷阱。全部回显实测于 Ubuntu 22.04.4 LTS + SurrealDB 3.3.0。

一、环境信息

  • 操作系统:Ubuntu 22.04.4 LTS(x86_64)
  • 数据库:SurrealDB 3.3.0 stable,服务监听 0.0.0.0:8000,rocksdb 后端(rocksdb:/opt/data/surrealdb/database.db)
  • 实测通道一:surreal sql -u root -p q1w2e3r4 --ns shop --db demo(CLI REPL,默认端点 ws://localhost:8000)
  • 实测通道二:curl -s http://127.0.0.1:8000/sql -u root:q1w2e3r4 -H 'surreal-ns: shop' -H 'surreal-db: demo' -d '<SurrealQL>'(HTTP)
  • 实测数据集:user 表(SCHEMALESS,含 alex/jamie/tianqi/zhaoliu/wangwu 五条记录)、post 表(SCHEMAFUL,三篇文章)、边表 wrote(作者→文章)与 references(文章→文章引用)
  • CLI 回显为 REPL 原样输出;HTTP 回显为 JSON(result/status/time 结构),两种通道语义一致

💡 技术拓展:同一套 SurrealQL 在两个通道的执行差异——CLI 每次回车按「一次连接会话」处理,管道逐行喂入时每行是独立隐式事务;HTTP /sql 一次请求体可携带多条语句并保证批量执行(每条语句独立返回 result/status)。跨语句原子事务的写法差异见第十章。

二、语言总览

本节先定位 SurrealQL 与 SQL、GraphQL 的异同,再给出 3.x 版本的语法变更提示,帮助读者建立整体认知。

2.1 与 SQL 的关系

SurrealQL 的表层语法接近 SQL(SELECT/FROM/WHERE/ORDER BY/LIMIT 一脉相承),但有四个结构性差异(均实测确认):

  1. 主键即记录 ID:user:alex 这样的 Record ID 是一等公民,可直接出现在语句任何位置(SELECT * FROM user:alex;),无需 WHERE 条件。
  2. 无 JOIN 关键字:关联数据用 FETCH(展开记录链接)或图方向查询 ->/<- 表达,而不是 JOIN ... ON。
  3. 模式定义也是查询语言:DEFINE TABLE/FIELD/INDEX/ACCESS 等与数据语句共用同一语言与通道,USE NS/DB 切换上下文。
  4. 图与全文内建:RELATE 建边、@@ 全文匹配、search::score() 打分都是语言内建能力。

2.2 与 GraphQL 的关系

SELECT name, author.name FROM post FETCH author; 的投影 + 展开形态与 GraphQL 的「按需取字段 + 嵌套解析」体验相似,但 SurrealQL 是服务端字符串执行(HTTP/WS 传语句),不是 schema 驱动的强类型查询协议;SDK 层另有 GraphQL 兼容能力(以官方文档为准,本文不展开)。

2.3 版本提示(3.x 语法变更)

3.0.0 起全文索引语法由 SEARCH ANALYZER 改为 FULLTEXT ANALYZER(官方文档 Note 明确),旧写法实测报语法错误(CLI 回显为带位置标注的 Parse error 块,HTTP JSON 响应体为 There was a problem with the database: Parse error: Expected \UNIQUE`, `COLUMNS`, or `FIELDS` but the token or string ends at line 1 column 63`)。网络上的旧教程多为 2.x 语法,照抄会踩坑。

三、语法基础

本节覆盖语句作用域、书写规则、字段类型与参数变量四项基础约定,后续所有语句都建立在这些约定之上。

3.1 层级与 USE

语句作用域由 命名空间(NS)→ 数据库(DB)→ 表(TB) 层级定位。会话开始先 USE:

USE NS shop DB demo;
[{ database: 'demo', namespace: 'shop' }]

也可在 CLI 用参数 --ns shop --db demo、在 HTTP 用请求头 surreal-ns/surreal-db 预设,省去 USE 语句。

3.2 语句书写规则

  • 语句以分号结尾;REPL 中多行查询行尾加反斜杠 \ 续行(实测横幅原文:To create a multi-line query, end your lines with a (\) backslash, and press enter.)。
  • 一次提交可携带多条分号分隔的语句,回显按顺序逐条返回数组元素。
  • 字符串用单引号或双引号;日期时间字面量前缀 d'2026-01-01T00:00:00Z'。

3.3 字段类型速览(实测涉及)

类型写法实测示例
字符串stringDEFINE FIELD name ON user TYPE string;
数值int / numberDEFINE FIELD score ON user TYPE number DEFAULT 0;
可空选项option<T>DEFINE FIELD age ON user TYPE option<int>;(⚠️ 实测 age = NULL 报错,空值语义是 NONE 缺省)
数组array<string>DEFINE FIELD tags ON user TYPE option<array<string>>;
时间datetimeDEFINE FIELD created_at ON user TYPE datetime DEFAULT time::now();
记录引用record<user>DEFINE FIELD author ON post TYPE record<user>;
任意any(缺省)SCHEMALESS 表未定义字段即任意类型

3.4 参数与变量

LET 定义会话级参数,DEFINE PARAM 定义库级参数,$auth/$scope 等为系统参数:

LET $min_score = 80;
SELECT id, name, score FROM user WHERE score >= $min_score;
DEFINE PARAM $env_name VALUE 'shop-demo';
RETURN $env_name;
[NONE]
[[{ id: user:alex, name: 'Alex Zhang', score: 97 }, { id: user:jamie, name: 'Jamie Li', score: 92 }, { id: user:wangwu, name: '王五', score: 81 }]]
[NONE]
['shop-demo']

系统参数实测:root 系统用户会话中 RETURN $auth; 返回 null;记录用户(RECORD ACCESS 登录)会话返回本人记录 ID(实测 person:d4sdynsd70o0izqpily3);v3 ACCESS 模型下 RETURN $scope; 返回 null(旧版 scope 概念已被 ACCESS 取代)。

四、DEFINE 语句族

DEFINE 负责库内全部模式对象(表/字段/分析器/索引/用户/访问方法)的定义,本节逐类给出语法、参数与实测行为。

4.1 DEFINE TABLE

DEFINE TABLE user SCHEMALESS;                    -- 自由模式
DEFINE TABLE post SCHEMAFUL;                     -- 严格模式
DEFINE TABLE OVERWRITE post SCHEMAFUL;           -- 覆盖式重定义
DEFINE TABLE person SCHEMALESS PERMISSIONS FULL; -- 表级权限(缺省 NONE)

参数说明:SCHEMALESS(TYPE ANY)允许任意字段;SCHEMAFUL(TYPE NORMAL)只允许已 DEFINE 的字段;OVERWRITE/IF NOT EXISTS 控制重复定义行为;PERMISSIONS 支持 NONE/FULL 或按 select/create/update/delete 分别写表达式(实测 SCHEMAFUL 表写入未定义字段报错:Found field 'created_at', but no such field exists for table 'post')。

4.2 DEFINE FIELD

DEFINE FIELD title ON post TYPE string ASSERT $value != NONE AND string::len($value) > 0;
DEFINE FIELD author ON post TYPE record<user>;
DEFINE FIELD created_at ON post TYPE datetime DEFAULT time::now();
DEFINE FIELD age ON user TYPE option<int> DEFAULT NULL;

参数说明:TYPE 限定类型;DEFAULT 缺省值表达式;ASSERT 写入断言(不满足即拒绝);VALUE 为强制计算列(每次写入重算)。实测行为:

  • ASSERT 生效:SCHEMAFUL 表 title 断言非空后,空标题写入被拒(类型与断言在写入路径执行)。
  • 陷阱回显:option<int> 字段显式写 NULL 被拒——Couldn't coerce value for field \age` of `user:duplicate`: Expected `none | int` but found `NULL`。注意实测发现另一细节:字段若以 DEFAULT NULL定义(如本例),**连省略字段也会触发同一报错**(缺省值计算出 NULL 再被类型拒绝);正确做法是定义时不带 DEFAULT(缺省即 NONE)或显式DEFAULT NONE`。

4.3 DEFINE ANALYZER(全文分析器)

DEFINE ANALYZER zh_class TOKENIZERS class FILTERS LOWERCASE;
DEFINE ANALYZER zh_ngram TOKENIZERS class FILTERS LOWERCASE, EDGENGRAM(2,5);
DEFINE ANALYZER zh_ngram2 TOKENIZERS class FILTERS LOWERCASE, NGRAM(2,5);

分词实测(search::analyze 直接观察):

RETURN search::analyze('zh_class', '数据库系统原理实战指南');
RETURN search::analyze('zh_ngram', '数据库系统原理实战指南');
RETURN search::analyze('zh_ngram', 'Hello SurrealDB World');
[['数据库系统原理实战指南']]
[['数据', '数据库', '数据库系', '数据库系统', '据库', '据库系', '据库系统', '据库系统原', '库系', '库系统', '库系统原', '库系统原理', '系统', '系统原', '系统原理', '系统原理实', '统原', '统原理', '统原理实', '统原理实战', '原理', '原理实', '原理实战', '原理实战指', '理实', '理实战', '理实战指', '理实战指南', '实战', '实战指', '实战指南', '战指', '战指南', '指南']]
[['hello', 'hell', 'hello', 'surr', 'surre', 'surrea', 'surreal', 'surreald', 'surrealdb', 'urre', 'urrea', 'urreal', 'urreald', 'urrealdb', 'rrea', 'rreal', 'rreald', 'rrealdb', 'worl', 'world', 'orld']]

要点:class 分词器对连续中文不切分(整串返回);中文检索必须叠加 NGRAM/EDGENGRAM 过滤器;过滤器拼写错误在解析期即被拒(实测 EDGNGRAM 漏一个 E 报 Parse error: Unexpected token \an identifier`, expected a filter),正确拼写 EDGENGRAM`。

4.4 DEFINE INDEX

-- 唯一索引
DEFINE INDEX idx_user_email ON TABLE user FIELDS email UNIQUE;
-- 全文索引(v3 语法:FULLTEXT ANALYZER ... BM25 HIGHLIGHTS)
DEFINE INDEX ft_post_title ON TABLE post FIELDS title FULLTEXT ANALYZER zh_ngram BM25(1.2, 0.75) HIGHLIGHTS;
DEFINE INDEX ft_post_content ON TABLE post FIELDS content FULLTEXT ANALYZER zh_ngram2 BM25;

参数说明:FIELDS/COLUMNS 等价;UNIQUE 冲突拒绝;FULLTEXT 指定分析器;BM25[(k1, b)] 相关性参数;HIGHLIGHTS 启用高亮支持。唯一冲突实测回显:

CREATE user:duplicate SET name = 'Dup User', email = 'alex@example.com', age = 22;
["Database index `idx_user_email` already contains 'alex@example.com', with record `user:alex`"]

4.5 DEFINE USER / DEFINE ACCESS(鉴权)

DEFINE USER viewer_user ON DATABASE PASSWORD 'q1w2e3r4' ROLES VIEWER;
DEFINE ACCESS acct ON DATABASE TYPE RECORD
  SIGNUP ( CREATE person SET name = $name, email = $user, pass = crypto::argon2::generate($pass) )
  SIGNIN ( SELECT * FROM person WHERE email = $user AND crypto::argon2::compare(pass, $pass) )
  DURATION FOR SESSION 12h, FOR TOKEN 1h;

实测行为:VIEWER 角色对 PERMISSIONS NONE 表读写均被拒(IAM error: Not enough permissions to perform this action),但 root 等 OWNER 级系统用户不受表权限限制;记录用户经 HTTP /signup /signin(认证体需含 NS/DB/AC 大写键与 user/pass)换取 JWT 后,$auth 即记录 ID。SIGNUP/SIGNIN 表达式访问的表必须 PERMISSIONS FULL(否则表达式被权限拦下恒失败,实测踩坑:PERMISSIONS NONE 的表导致 signin 返回 401)。

4.6 INFO FOR(查看定义)

INFO FOR TABLE post;
INFO FOR DB;
[{ events: {  }, fields: { author: 'DEFINE FIELD author ON post TYPE record<user> PERMISSIONS FULL', content: 'DEFINE FIELD content ON post TYPE string PERMISSIONS FULL', created_at: 'DEFINE FIELD created_at ON post TYPE datetime DEFAULT time::now() PERMISSIONS FULL', title: 'DEFINE FIELD title ON post TYPE string ASSERT $value != NONE AND string::len($value) > 0 PERMISSIONS FULL' }, indexes: { ft_post_content: 'DEFINE INDEX ft_post_content ON post FIELDS content FULLTEXT ANALYZER zh_ngram2 BM25(1.2,0.75)', ft_post_title: 'DEFINE INDEX ft_post_title ON post FIELDS title FULLTEXT ANALYZER zh_ngram BM25(1.2,0.75) HIGHLIGHTS' }, lives: {  }, tables: {  } }]
[{ accesses: {  }, analyzers: {  }, apis: {  }, buckets: {  }, configs: {  }, functions: {  }, models: {  }, modules: {  }, params: {  }, sequences: {  }, tables: { post: 'DEFINE TABLE post TYPE NORMAL SCHEMAFULL PERMISSIONS NONE', user: 'DEFINE TABLE user TYPE ANY SCHEMALESS PERMISSIONS NONE' }, users: {  } }]

要点:INFO FOR TABLE 回显字段/索引/事件/订阅四类定义(PERMISSIONS FULL 来自 4.5 节 RECORD ACCESS 踩坑后的修正);INFO FOR DB 回显访问方法、分析器、参数、表、用户等九类定义。

五、CREATE 与 INSERT 语句

本节覆盖单条创建、批量创建与幂等写入三种写入形态,每条语句均附真实回显。

5.1 CREATE:单条创建(可指定或自动 ID)

CREATE user:alex SET name = 'Alex Zhang', email = 'alex@example.com', age = 30, score = 88, tags = ['admin', 'dev'], created_at = time::now();
CREATE user SET name = 'AutoId', email = 'auto2@example.com', age = 20;
[[{ age: 30, created_at: d'2026-10-17T09:58:06.805232168Z', email: 'alex@example.com', id: user:alex, name: 'Alex Zhang', score: 88, tags: ['admin', 'dev'] }]]
[[{ age: 20, created_at: d'2026-10-17T10:58:19.241998421Z', email: 'auto2@example.com', id: user:jocg11bvq47qhsojfhtf, name: 'AutoId', score: 0 }]]

参数说明:CREATE <表>:<ID> 显式指定主键;省略 ID 则自动生成随机 ID;SET 逐字段赋值;DEFAULT 字段(created_at)在未提供时自动填充。

5.2 INSERT:批量创建

INSERT INTO user { id: user:tianqi, name: '田七', email: 'tianqi@example.com', age: 28, score: 75, tags: ['ops', 'dev'] };
INSERT INTO user [
  { id: user:zhaoliu, name: '赵六', email: 'zhaoliu@example.com', age: 35, score: 60, tags: [] },
  { id: user:wangwu, name: '王五', email: 'wangwu@example.com', age: 40, score: 81, tags: ['audit'] }
];
[[{ age: 28, created_at: d'2026-10-17T09:58:06.809608808Z', email: 'tianqi@example.com', id: user:tianqi, name: '田七', score: 75, tags: ['ops', 'dev'] }]]
[[{ age: 35, ... id: user:zhaoliu, ... }, { age: 40, ... id: user:wangwu, ... }]]

(输出节选:第二条 INSERT 返回的两条完整记录以 ... 省略中间字段。)

5.3 INSERT ON DUPLICATE KEY UPDATE:幂等写入

INSERT INTO user { id: user:alex, name: 'Alex Zhang', email: 'alex@example.com', age: 32, score: 100 }
  ON DUPLICATE KEY UPDATE score = 100;
[{"result":[{"age":31,"created_at":"2026-10-17T09:58:06.805232168Z","email":"alex@example.com","id":"user:alex","level":"senior","name":"Alex Zhang","score":100,"tags":["admin","dev","reviewer"]}],"status":"OK","time":"1.050469ms","type":null}]

要点:记录 ID 已存在时不再报错,转而执行 UPDATE 子句(实测 alex.score 由 99 变 100,其余字段保留原值)。

六、SELECT 语句

SELECT 是读取路径的核心,本节按「基础过滤 → 投影 → 关联展开 → 图遍历 → 全文检索 → 聚合 → 执行计划」七个层次逐项实测。

6.1 基础查询:WHERE / ORDER BY / LIMIT / START

SELECT * FROM user WHERE score > 70 ORDER BY score DESC;
SELECT name, age, score FROM user WHERE tags CONTAINS 'dev' ORDER BY age ASC LIMIT 2 START 0;
SELECT id, name, score FROM user ORDER BY score DESC LIMIT 3;
[[{ id: user:alex, age: 30, created_at: ..., email: ..., name: 'Alex Zhang', score: 97, tags: [...] }, { id: user:jamie, ... score: 92 ... }, ...]]
[[{ name: 'Jamie Li', age: 25, score: 92 }, { name: '田七', age: 28, score: 75 }]]
[[{ id: user:alex, name: 'Alex Zhang', score: 97 }, { id: user:jamie, name: 'Jamie Li', score: 92 }, { id: user:wangwu, name: '王五', score: 81 }]]

(输出节选:第一条查询返回全部命中记录,完整字段以 ... 省略。)

参数说明:CONTAINS 为数组包含运算符(CONTAINSANY/CONTAINSALL 处理多值);LIMIT n START m 组合成分页。

6.2 投影与别名

SELECT name, age, score FROM user WHERE score > 90;
SELECT value name FROM user WHERE score > 90;
[[{ age: 30, name: 'Alex Zhang', score: 97 }, { age: 25, name: 'Jamie Li', score: 92 }]]
[['Alex Zhang', 'Jamie Li']]

SELECT value <字段> 直接返回值数组(去壳),配合参数提取常用。

6.3 FETCH:记录链接展开

SELECT id, title, author.name AS author_name, author.email AS author_email FROM post FETCH author;
[[{ author_email: 'alex@example.com', author_name: 'Alex Zhang', id: post:set_up_db, title: 'SurrealDB 数据库安装与初始化实战' }, ...]]

(输出节选:共命中三篇文章,后两条以 ... 省略。)

要点:author 字段存的是 user:alex 引用,FETCH 后在结果集中展开为完整记录,投影可直接引用展开后的 author.name。不 FETCH 时同查询返回裸引用(author: user:alex)。

6.4 图方向查询(正反两向)

SELECT ->wrote->post.title AS titles FROM user:alex;
SELECT <-wrote<-user.name AS writers FROM post:graph_model;
SELECT value ->wrote->post FROM user:alex;
[[{ titles: ['SurrealQL 查询语言语法速查', 'SurrealDB 数据库安装与初始化实战'] }]]
[[{ writers: ['Jamie Li'] }]]
[[[post:query_lang, post:set_up_db]]]

参数说明:->边表->目标表 正向遍历,<-边表<-源表 反向遍历;SELECT value 与图路径组合返回目标 ID 数组。更多图查询(多跳、边属性)见第八章。

6.5 全文检索:@@ 与打分高亮

SELECT id, title FROM post WHERE title @@ '数据库';
SELECT id, title, search::score(1.0) AS score FROM post WHERE title @@ '数据库安装' ORDER BY score DESC;
SELECT id, title, search::highlight('<b>', '</b>', 1.0) AS marked, search::offsets(1.0) AS offsets
  FROM post WHERE title @@ '数据库安装';
SELECT id, title, search::highlight('<b>', '</b>', 1.0) AS highlighted
  FROM post WHERE title @@ '数据库' AND content @@ '图关系';
[[{ id: post:set_up_db, title: 'SurrealDB 数据库安装与初始化实战' }]]
[[{ id: post:set_up_db, score: 0.4721917510032654f, title: 'SurrealDB 数据库安装与初始化实战' }]]
[[{ id: post:set_up_db, marked: 'SurrealDB <b>数据库安装与初始化实战</b>', offsets: { "0": [{ e: 21, s: 10 }] }, title: 'SurrealDB 数据库安装与初始化实战' }]]
[[{ highlighted: 'SurrealDB <b>数据库</b>安装与初始化实战', id: post:set_up_db, title: 'SurrealDB 数据库安装与初始化实战' }]]

参数说明:@@ 走全文倒排索引(依赖 4.3/4.4 定义的分析器与索引);search::score(1.0) 的参数是索引序号引用;highlight/offsets 返回高亮文本与命中偏移(s/e 为字节起止)。

6.6 聚合与分组

SELECT count() AS total, math::mean(score) AS avg_score FROM user GROUP ALL;
SELECT id, content FROM post WHERE content @@ '查询语言';
[[{ avg_score: 79.2f, total: 5 }]]
[[{ content: '整理查询语言常用语法,包括创建、查询、更新与删除等语句。', id: post:query_lang }]]

GROUP ALL 全表聚合计数与均值;GROUP BY <字段> 按维度分组(同类语法)。

6.7 EXPLAIN:执行计划

SELECT * FROM user WHERE email = 'alex@example.com' EXPLAIN;
SELECT * FROM user WHERE name = 'Alex Zhang' EXPLAIN;
[{"result":{"attributes":{"projections":"*"},"children":[{"attributes":{"access":"= 'alex@example.com'","direction":"Forward","index":"idx_user_email"},"context":"Db","operator":"IndexScan"}],"context":"Db","operator":"SelectProject"},"status":"OK","time":"487.074µs","type":null},
 {"result":{"attributes":{"projections":"*"},"children":[{"attributes":{"direction":"Forward","pre_decode_filter":"yes","predicate":"name = 'Alex Zhang'","table":"user"},"context":"Db","operator":"TableScan"}],"context":"Db","operator":"SelectProject"},"status":"OK","time":"151.6µs","type":null}]

要点:email 命中唯一索引走 IndexScan(access 表达式 = 'alex@example.com');name 无索引走 TableScan 全表扫描(predicate 下推过滤)。执行计划是第十三章性能优化的核心观测手段。

七、UPDATE、PATCH、UPSERT 与 DELETE

本节覆盖四种数据变更形态:整写/合并更新、JSON Patch 增量修改、存在性自适应的 UPSERT、以及记录/表级删除。

7.1 UPDATE:整写与合并

UPDATE user:alex SET score = 95;                            -- 指定记录
UPDATE user:alex SET age = 30, score = 96 WHERE score > 90; -- 条件更新
UPDATE user:alex MERGE { tags: ['admin', 'dev', 'reviewer'] }; -- 深合并
[[{ age: 30, created_at: ..., email: ..., id: user:alex, name: 'Alex Zhang', score: 95, tags: ['admin', 'dev'] }]]

(输出节选:created_at/email 等未变更字段值省略。)

参数说明:SET 整体赋值;MERGE 对象合并(保留未提及字段);UNSET <字段> 删除字段(同类语法)。

7.2 PATCH:JSON Patch 标准操作

UPDATE user:alex PATCH [
  { op: 'add', path: '/level', value: 'senior' },
  { op: 'replace', path: '/score', value: 97 }
];
SELECT * FROM user:alex;
[[{ age: 30, created_at: d'2026-10-17T09:58:06.805232168Z', email: 'alex@example.com', id: user:alex, level: 'senior', name: 'Alex Zhang', score: 97, tags: ['admin', 'dev', 'reviewer'] }]]
[[{ age: 30, created_at: d'2026-10-17T09:58:06.805232168Z', email: 'alex@example.com', id: user:alex, level: 'senior', name: 'Alex Zhang', score: 97, tags: ['admin', 'dev', 'reviewer'] }]]

参数说明:PATCH 接受 JSON Patch 操作数组(add/remove/replace/move/copy/test),按路径精确修改,适合客户端增量提交。

7.3 UPSERT:存在则更新、不存在则创建

UPSERT user:alex SET name = 'Alex Zhang', email = 'alex@example.com', age = 31, score = 99;
UPSERT user:newcomer SET name = 'New', email = 'new@example.com', age = 20, score = 10;
[{"result":[{"age":31,"created_at":"2026-10-17T09:58:06.805232168Z","email":"alex@example.com","id":"user:alex","level":"senior","name":"Alex Zhang","score":99,"tags":["admin","dev","reviewer"]}],"status":"OK","time":"1.461442ms","type":null},
 {"result":[{"age":20,"created_at":"2026-10-17T10:10:00.101386505Z","email":"new@example.com","id":"user:newcomer","name":"New","score":10}],"status":"OK","time":"965.413µs","type":null}]

7.4 DELETE 与 REMOVE

DELETE user:tx_test2;          -- 删除记录(可带 WHERE)
SELECT * FROM user:tx_test2;   -- 验证已删
REMOVE TABLE person;           -- 删除整表(含定义与数据)
[{"result":[],"status":"OK","time":"1.210055ms","type":null},{"result":[],"status":"OK","time":"191.42µs","type":null},{"result":null,"status":"OK","time":"1.151464ms","type":null}]

要点:DELETE 删数据留表定义,REMOVE TABLE 连定义一起删;REMOVE FIELD/INDEX/ANALYZER/ACCESS 等同族语句删除对应定义。

八、RELATE 与图查询

本节演示带属性边的建立、多跳与双向遍历、图路径与 FETCH 的组合,全部语句在实测数据集上执行。

8.1 RELATE:建立带属性的边

RELATE user:alex->wrote->post:set_up_db SET role = 'lead', at = time::now();
RELATE post:graph_model->references->post:set_up_db SET note = '延伸阅读';
[[{ at: d'2026-10-17T09:58:06.818700886Z', id: wrote:tn91oj6bzxzxpg9f5lt4, in: user:alex, out: post:set_up_db, role: 'lead' }]]

参数说明:RELATE <入点>-><边表>-><出点> 创建边记录,自动生成随机边 ID,in/out 记录两端点,SET 附加边属性。边表可 DEFINE FIELD 约束(同普通表)。

8.2 多跳遍历与双向查询

RELATE post:query_lang->references->post:set_up_db SET note = '相关篇目';
SELECT ->wrote->post->references->post.title AS chain FROM user:alex;
SELECT <-wrote<-user.name AS writers, <-references<-post.id AS referenced_by FROM post:set_up_db;
[[{ chain: ['SurrealDB 数据库安装与初始化实战'] }]]
[[{ referenced_by: [post:graph_model, post:query_lang], writers: ['Alex Zhang'] }]]

两跳链 user→wrote→post→references→post 实测命中(alex 写的文章被引用边指向 set_up_db);反向一次取回引用方与作者。

8.3 图路径 + FETCH 组合

SELECT * FROM user:alex FETCH ->wrote->post;
[[{ age: 31, created_at: ..., id: user:alex, ..., ->wrote: [{ at: d'2026-10-17T09:58:06.818700886Z', id: wrote:tn91oj6bzxzxpg9f5lt4, in: user:alex, out: { author: user:alex, content: '本文讲解数据库安装、初始化与持久化配置,涵盖 rocksdb 存储引擎要点。', id: post:set_up_db, title: 'SurrealDB 数据库安装与初始化实战', ... }, role: 'lead' }] }]]

(输出节选:user:alex 的完整字段与 ->wrote 数组内多条边以 ... 省略。)

要点:FETCH 图路径把边记录与其 out 目标记录一并展开(边属性 role/at 可见);注意目标记录内的 author 引用未继续展开——图路径 FETCH 默认一层,嵌套展开需在 FETCH 中继续声明。

九、LIVE SELECT、KILL 与事件

本节实测实时订阅的建立、推送观察与注销,并明确其通道边界(仅 WS 有推送语义)。

9.1 LIVE SELECT:实时订阅

LIVE SELECT * FROM user;                       -- 表级订阅
LIVE SELECT * FROM user WHERE name = $value;   -- 条件订阅(实测可建立并返回 UUID)
[u'adb75583-e2ec-49ff-bbf1-c4e5644cfd52']

订阅后同一表发生变更时,本连接即时收到推送(另一连接 CREATE user:live_probe ... 后,实测推送报文):

{ action: 'CREATE', id: u'adb75583-e2ec-49ff-bbf1-c4e5644cfd52', result: { age: 18, created_at: d'2026-10-17T10:00:26.478788350Z', email: 'live@example.com', id: user:live_probe, name: 'LiveProbe', score: 1 } }

要点:LIVE SELECT 只在 WebSocket 通道(CLI/SDK)有推送语义;HTTP /sql 无推送。断线重连后需重新订阅并补拉增量(订阅不跨连接保留)。

9.2 KILL:注销订阅

KILL u'adb75583-e2ec-49ff-bbf1-c4e5644cfd52';
[]

实测执行后无报错(回显空结果集),该订阅不再推送(连接 B 后续写入无通知)。

十、事务与控制语句

本节实测事务的三条终止路径与 CLI 管道模式的隐式事务陷阱,给出原子操作的正确写法。

10.1 原子事务(单查询内)

BEGIN TRANSACTION; CREATE user:tx_test4 SET name = 'T4', email = 't4@example.com', age = 20, score = 50; UPDATE user:tx_test4 SET score = 66; CANCEL TRANSACTION;
SELECT * FROM user:tx_test4;
BEGIN TRANSACTION; CREATE user:tx_test5 SET name = 'T5', email = 't5@example.com', age = 21, score = 50; COMMIT TRANSACTION;
SELECT * FROM user:tx_test5;
BEGIN TRANSACTION; CREATE user:tx_test6 SET name = 'T6', email = 't6@example.com', age = 22, score = 50; THROW 'abort_this_txn'; COMMIT TRANSACTION;
SELECT * FROM user:tx_test6;
[NONE, 'The query was not executed due to a cancelled transaction', 'The query was not executed due to a cancelled transaction', NONE]
[[]]
[NONE, NONE]
[[{ age: 21, created_at: d'2026-10-17T09:59:32.233976214Z', email: 't5@example.com', id: user:tx_test5, name: 'T5', score: 50 }]]
[NONE, 'The query was not executed due to a failed transaction', 'An error occurred: abort_this_txn', 'Cannot COMMIT: the transaction was aborted due to a prior error']
[[]]

参数说明:BEGIN TRANSACTION; 与 COMMIT/CANCEL TRANSACTION; 必须写在同一条查询里(分号分隔);THROW <消息> 抛出异常并回滚。实测结论:CANCEL 显式回滚与 THROW 异常回滚都不落库(tx_test4/tx_test6 查询为空)。

10.2 CLI 管道陷阱(真实报错)

逐行喂给 surreal sql 时每行是独立隐式事务,跨行 BEGIN/CANCEL 实测报错:

BEGIN TRANSACTION;   →  [NONE]
CREATE user:tx_test ...;  →  已落库
CANCEL TRANSACTION;  →  ['Invalid statement: Cannot CANCEL without starting a transaction']

结论:多语句原子操作要么写成一条查询,要么走 HTTP /sql 一次请求体批量提交。

十一、场景化查询模式(实测模板)

以下五个模板对应最常见的业务查询场景,均可直接套用本数据集复现。

11.1 图谱查询:作者→文章→引用链

SELECT ->wrote->post->references->post.title AS chain FROM user:alex;
[[{ chain: ['SurrealDB 数据库安装与初始化实战'] }]]

适用:知识图谱、社交关系、内容推荐链路。

11.2 列表页:文档 + 作者一次取回

SELECT id, title, author.name AS author_name, author.email AS author_email FROM post FETCH author;
[[{ author_email: 'alex@example.com', author_name: 'Alex Zhang', id: post:set_up_db, title: 'SurrealDB 数据库安装与初始化实战' }, { author_email: 'jamie@example.com', author_name: 'Jamie Li', id: post:graph_model, title: '图关系建模从入门到实践' }, { author_email: 'alex@example.com', author_name: 'Alex Zhang', id: post:query_lang, title: 'SurrealQL 查询语言语法速查' }]]

适用:CMS 列表页,替代 SQL JOIN。

11.3 全文检索:BM25 排序 + 高亮 + 多字段过滤

SELECT id, title, search::score(1.0) AS score, search::highlight('<b>', '</b>', 1.0) AS marked
  FROM post WHERE title @@ '数据库' AND content @@ '图关系'
  ORDER BY score DESC;
[[{ highlighted: ..., id: post:set_up_db, marked: ..., score: ..., title: ... }]]

(输出节选:各字段值同 6.5 节实测形态,此处省略。)

(本模板为 6.5 节实测语句的排序组合;单条执行回显见 6.5。)

11.4 幂等初始化:INSERT ON DUPLICATE KEY UPDATE

INSERT INTO user { id: user:alex, name: 'Alex Zhang', email: 'alex@example.com', age: 32, score: 100 }
  ON DUPLICATE KEY UPDATE score = 100;

适用:配置/种子数据重复执行不产生脏数据(实测回显见 5.3)。

11.5 分页浏览

SELECT name, age, score FROM user WHERE tags CONTAINS 'dev' ORDER BY age ASC LIMIT 2 START 0;
[[{ name: 'Jamie Li', age: 25, score: 92 }, { name: '田七', age: 28, score: 75 }]]

下一页 START 2,配合 count() 估算总数。

十二、函数与运算符速查(实测)

以下均为 3.3.0 实测结果(CLI/HTTP 双通道验证,取样代表性函数):

类别函数/运算符实测调用 → 结果
数学math::sum / meanmath::sum([1,2,3,4]) → 10;math::mean([1,2,3,4]) → 2.5
数学math::round / ceil / floormath::round(3.56) → 4.0;math::ceil(2.1) → 3.0;math::floor(2.9) → 2.0
数学math::sqrt / pow / abs / max / medianmath::sqrt(16) → 4.0;math::pow(2,10) → 1024;math::abs(-7) → 7;math::max([3,9,1]) → 9;math::median([1,2,3,4]) → 2.5
时间time::now / unixtime::now() → 2026-10-17T10:08:40.564568613Z;time::unix() → 1792231720
时间time::year / month / day / hourtime::year(time::now()) → 2026;month → 10;day → 17;hour → 10
时间time::floortime::floor(time::now(), 1h) → 2026-10-17T10:00:00Z
时间⚠️ time::epoch3.3.0 实测不可用(字符串与 datetime 参数均报 There was a problem running the ANONYMOUS() function. 'datetime' is not a function),时间戳请用 time::unix
字符串string::uppercase / len / containsstring::uppercase('surrealdb') → SURREALDB;string::len('数据库') → 3;string::contains('SurrealDB 实战', '实战') → true
数组array::len / distinctarray::len(['a','b','c']) → 3;array::distinct([1,2,2,3]) → [1,2,3]
随机rand::uuid::v4→ 9a727679-92a3-4f9b-9e0d-926f9fb86203
密码crypto::argon2::generate / compareRECORD ACCESS 实测使用(4.5 节),生成/校验 argon2 哈希
全文search::analyze / score / highlight / offsets分词、打分、高亮、偏移(实测见 4.3/6.5)
运算符@@全文匹配(走 FULLTEXT 索引)
运算符CONTAINS数组/字符串包含:WHERE tags CONTAINS 'dev' 实测命中
运算符-> / <-图方向遍历(正/反向,实测见 6.4/8.2)

十三、性能与陷阱

本节汇总影响查询性能与正确性的五个实测陷阱,每条附验证方法与规避动作。

13.1 无索引全扫(EXPLAIN 实测)

WHERE name = 'Alex Zhang' 无索引走 TableScan(13 之前的 6.7 节实测计划),记录量增大后线性劣化;WHERE email = ... 命中 IndexScan。优化动作:为高频过滤字段 DEFINE INDEX ... FIELDS <字段>(或 UNIQUE),并用 EXPLAIN 复核确实命中。

13.2 FETCH 深度

FETCH 与图路径展开默认一层(8.3 节实测:out 内的 author 未继续展开)。深层展开需显式链式声明,嵌套过深会放大取回量——列表页建议投影 + 别名只取所需字段,不要 SELECT * FETCH 全展开。

13.3 大事务与管道陷阱

  • 事务必须单查询内完成(10.1 节);CLI 管道逐行执行时事务不跨行(10.2 节实测报错)。
  • 大批量写入优先 INSERT 数组批量而非逐条 CREATE(减少往返),超大批量可分块 + 事务包裹。

13.4 NGRAM 索引体积与召回

中文 NGRAM(2,5) 全量滑窗产生的词条数量大(4.3 节实测单句 33 个 gram),索引体积随文本量明显膨胀;EDGENGRAM 只保留左锚定 gram 体积更小(前缀命中)。建议:标题类短文本用 NGRAM,正文类长文本评估 EDGENGRAM 或加大 N 下限。

13.5 类型与语法陷阱清单(实测原文)

  • option<int> 字段写 NULL → Couldn't coerce value for field \age` of `user:duplicate`: Expected `none | int` but found `NULL`(字段不带 DEFAULT 时,省略字段用缺省 NONE 即可;DEFAULT NULL` 定义下连省略也会报同错)。
  • SCHEMAFUL 表写未定义字段 → Found field 'created_at', but no such field exists for table 'post'。
  • 唯一索引冲突 → Database index \idx_user_email` already contains 'alex@example.com', with record `user:alex``。
  • 2.x 全文语法照抄 → Parse error: Expected \UNIQUE`, `COLUMNS`, or `FIELDS` but the token or string ends at line 1 column 63`(CLI 回显为带位置标注的错误块,改用 FULLTEXT ANALYZER)。
  • 分析器拼写错误 → Parse error: Unexpected token \an identifier`, expected a filter`(解析期被拒,正确拼写 EDGENGRAM)。
  • 权限不足 → IAM error: Not enough permissions to perform this action(检查角色与表 PERMISSIONS)。

十四、参考链接