在传统社交网络中,每一次点赞、评论或发帖都被精心包装成友好的 UI 交互,背后却是简单的数据库操作。SQLNet.cc 反其道而行之,将 SQL 查询直接作为用户界面,让用户通过编写 SQL 语句来参与社交互动。这种设计不仅是对社交网络本质的回归,更是一次数据库架构的极限挑战。
三数据库模型:集体幻觉的工程实现
SQLNet 的核心架构被称为 "三数据库模型",旨在为每个用户提供独立的数据库体验,同时维持统一的社交网络感知。
系统数据库:身份验证的守门人
系统数据库负责处理账户管理、密码验证和租户映射。用户不直接与之交互,它仅作为后端服务的身份验证层存在。这种分离确保了核心业务逻辑与身份管理的解耦。
主数据库:唯一真相源
主数据库存储所有社交行为的规范记录,采用简洁的规范化模式:
posts(id, author_id, content, created_at, updated_at)
likes(id, post_id, user_id, created_at)
comments(id, post_id, user_id, content, created_at, updated_at)
follows(follower_id, following_id, created_at)
这个数据库不包含任何衍生字段,如like_count或comment_count,确保数据的一致性和可追溯性。
租户数据库:个性化的幻觉空间
每个用户注册时,系统会为其分配一个完整的 SQLite 数据库(通过 Turso 提供)。这些数据库不仅包含主数据库的所有数据,还添加了便利字段:
posts(id, author_id, content, created_at, updated_at, like_count, comment_count)
这种设计允许用户编写更简洁的查询,例如ORDER BY like_count DESC,而无需执行复杂的 JOIN 操作。用户直接与自己的数据库交互,但所有数据都通过同步机制与主数据库保持一致性。
CDC 同步引擎:Turso 不稳定特性的工程化应用
SQLNet 最核心的技术挑战是实现数千个独立数据库之间的实时同步。解决方案依赖于 Turso 的 Change Data Capture(CDC)功能,具体通过一个不稳定的 PRAGMA 实现。
CDC 配置的关键细节
每个数据库连接都必须执行以下命令:
PRAGMA unstable_capture_data_changes_conn('full');
这个unstable_前缀明确表明该功能尚未稳定,可能在未来的 Turso 版本中被修改或移除。但正是这个功能为 SQLNet 提供了必要的变更捕获能力。
执行该 PRAGMA 后,Turso 会创建一个特殊的turso_cdc表,记录所有数据变更。读取变更的查询如下:
SELECT
table_name,
change_type, -- 1=INSERT, 0=UPDATE, -1=DELETE
id,
bin_record_json_object(table_columns_json_array(table_name), before) as before_json,
bin_record_json_object(table_columns_json_array(table_name), after) as after_json
FROM turso_cdc
ORDER BY change_id ASC
其中bin_record_json_object和table_columns_json_array是 Turso 的内部函数,用于将二进制记录转换为 JSON 格式。
连接管理的陷阱
关键工程经验:CDC PRAGMA 必须在每个连接上执行,而不是每个数据库。如果某个连接池漏掉了这个步骤,该连接将无法捕获变更,导致同步 "随机" 失效。SQLNet 开发者曾花费 4 小时调试这个问题,最终发现是一个连接池配置错误。
正确的 Go 实现:
db, err := sql.Open("turso", dbPath)
if err != nil {
return nil, err
}
// 必须在每个连接上执行
_, err = db.Exec("PRAGMA unstable_capture_data_changes_conn('full');")
双向同步机制:上游与下游的数据流
SQLNet 的同步系统分为两个方向,每个方向都有独特的挑战和解决方案。
上游同步:用户→主数据库
当用户在自己的数据库中执行操作时,同步流程如下:
- 变更捕获:读取用户数据库的
turso_cdc表 - 来源过滤:排除来自主数据库的变更(防止循环复制)
- 字段过滤:移除非规范字段(如
like_count) - 写入主库:将规范化的变更写入主数据库
- 统计更新:重新计算受影响帖子的统计信息
- 清理 CDC:清空用户的 CDC 表
字段过滤是关键的安全措施:
func (r *Replicator) isCanonicalField(field string) bool {
excludedFields := map[string]bool{
"like_count": true,
"comment_count": true,
"_sync_origin": true,
}
return !excludedFields[field]
}
下游同步:主数据库→所有其他用户
下游同步是一个更复杂的广播过程:
- 读取主库变更:定期扫描主数据库的 CDC 表
- 识别来源:确定每个变更的发起用户
- 广播写入:对除发起者外的所有租户执行
INSERT OR REPLACE - 清理主 CDC:清空主数据库的 CDC 表
- 循环检测:通过
_sync_origin字段标记下游写入
循环检测的实现:
func isReplicatedChange(change CDCChange) bool {
if change.Operation == "INSERT" {
if origin, ok := change.NewData["_sync_origin"].(string); ok {
return origin == "primary"
}
}
// UPDATE: was null before, is "primary" now? That's a sync write.
if change.Operation == "UPDATE" {
oldOrigin, _ := change.OldData["_sync_origin"].(string)
newOrigin, _ := change.NewData["_sync_origin"].(string)
return oldOrigin == "" && newOrigin == "primary"
}
return false
}
权限控制与安全策略
me () 函数:上下文感知的查询注入
SQLNet 引入了一个特殊的me()函数,在查询执行时自动注入当前用户的 ID:
-- 用户查询自己的帖子
SELECT * FROM posts WHERE author_id = me();
-- 查看谁关注了自己
SELECT * FROM follows WHERE following_id = me();
后端实现会拦截 SQL 查询,将me()替换为从 JWT 令牌中提取的实际用户 ID。这种设计既保持了查询的简洁性,又确保了数据隔离。
本地表:用户的私有空间
并非所有表都参与同步。用户可以创建仅存在于自己数据库中的表:
-- 草稿表,仅本地可见
CREATE TABLE drafts (
id TEXT PRIMARY KEY,
content TEXT,
created_at DATETIME
);
-- 保存的查询,个性化算法
CREATE TABLE saved_queries (
id TEXT PRIMARY KEY,
name TEXT,
query TEXT
);
这些表完全由用户控制,上游同步会忽略它们,为用户提供了真正的私有空间。
"破坏性" 功能:自我修正的幻觉
SQLNet 允许用户修改非规范字段,如将like_count设置为任意值:
UPDATE posts SET like_count = 999999 WHERE id = 'some-post';
这种修改会在用户的数据库中生效,但不会传播到主数据库。当下游同步运行时,真实的统计信息会覆盖用户的修改。这种设计创造了一种 "数字涂鸦" 体验 —— 用户可以暂时修改现实,但系统最终会恢复真相。
扩展性挑战与优化方向
当前架构的复杂度分析
下游同步的复杂度为 O (变更数 × 租户数)。假设有 1,000 个用户,每个同步周期有 100 个变更,那么需要执行 100,000 次数据库写入。随着用户数量增长,这种复杂度将变得不可持续。
优化策略建议
- 批量处理:将多个变更合并为单个事务,减少网络往返
- 分区同步:将用户分组,只向相关组广播变更
- 增量 CDC:只同步自上次同步以来的变更,而不是全量扫描
- 连接池优化:复用数据库连接,减少 CDC 配置开销
- 异步队列:使用消息队列缓冲同步任务,平滑负载
分阶段扩展方案
- 阶段 1(<1,000 用户):当前架构足够
- 阶段 2(1,000-10,000 用户):引入批量处理和分区
- 阶段 3(>10,000 用户):重构为基于事件的架构,使用专门的同步服务
工程实践建议
CDC 使用的注意事项
- 版本兼容性:
unstable_capture_data_changes_conn可能在未来版本中变更,需要密切跟踪 Turso 更新 - 连接管理:确保所有连接池都正确配置 CDC PRAGMA
- 错误处理:实现健壮的重试机制,处理 CDC 读取失败的情况
- 监控指标:跟踪 CDC 表大小、同步延迟和失败率
数据库设计最佳实践
- 明确字段分类:严格区分规范字段和衍生字段
- 版本化模式:为每个表添加
_version字段,支持乐观并发控制 - 审计日志:保留关键操作的审计记录,便于调试和合规
- 索引策略:为同步查询创建合适的索引,特别是 CDC 表的
change_id
安全加固措施
- SQL 注入防护:虽然用户编写 SQL,但仍需验证查询的合法性
- 速率限制:防止恶意用户通过大量查询耗尽资源
- 查询复杂度限制:限制 JOIN 数量和结果集大小
- 敏感数据过滤:确保用户无法访问其他用户的私有信息
结论:SQL 作为交互语言的未来
SQLNet 展示了将数据库查询语言直接作为用户界面的可能性。这种设计虽然增加了用户的学习成本,但提供了前所未有的透明度和控制力。对于技术社区而言,SQLNet 不仅是一个社交网络,更是一个数据库架构的实验场。
CDC 同步机制虽然目前依赖于不稳定的 Turso 特性,但为多租户数据库同步提供了有价值的参考模式。随着边缘计算和分布式数据库的发展,类似的架构模式可能在更多场景中找到应用。
最终,SQLNet 的成功不在于用户规模,而在于它挑战了我们对 "用户友好" 的传统认知。在算法推荐和 UI 抽象盛行的时代,直接暴露底层操作可能正是某些用户渴望的 "友好" 体验。
资料来源:
- SQLNet 官方网站:https://sqlnet.cc
- "I Built a Social Network Where You Have to Write SQL to Post" (Dev.to, 2026-01-03)
- Turso 文档:Data & Connections 章节