6.4 数据库设计与优化 转载

来源:https://github.com/datawhalechina/vibe-vibe

本节目标:学会用 AI 交叉论证法设计健壮的数据库,理解索引、安全、连接管理等关键概念,能在应用变慢时知道从哪里下手。

AI 交叉论证法(炼蛊)

新手很难一眼看出 Schema 设计的好坏。你让 AI 设计了一套表结构,看着挺合理,但真的没问题吗?数据类型选对了吗?外键列有索引吗?约束完整吗?有没有性能隐患?

你自己可能看不出来,但另一个 AI 往往能发现问题。这就是"交叉论证法"的核心思路:

  1. AI 1 号 根据你的需求设计表结构
  2. 把生成的代码发给 AI 2 号,让它以"资深 PostgreSQL 架构师"的身份审查
  3. 根据 AI 2 号的反馈,让 AI 1 号修改
  4. 重复 1-2 轮,得到健壮的设计

为什么这个方法有效?因为每个 AI 都有自己的"盲点"。AI 1 号设计表结构时,可能专注于业务逻辑的正确性,忽略了性能优化;AI 2 号以审查者的视角来看,更容易发现"这个外键没索引"、"这个列类型不对"之类的问题。

这就像写代码时的 Code Review——自己写的代码自己很难发现 bug,但同事一眼就能看出来。AI 之间的交叉审查也是同样的道理。

通常经过两轮"左右互搏",你就能得到一个相当健壮的数据库设计。不需要你自己是数据库专家,只需要会"让 AI 互相挑毛病"。

行级安全(RLS):防止数据泄露的最后一道墙

它解决什么问题

想象小红的外卖平台出了个 bug:某个 API 接口忘了加权限检查,任何用户都能看到其他人的订单——包括收货地址、手机号、消费记录。

如果权限控制只在代码里做,一个 bug 就能导致全部用户数据泄露。而代码里的 bug 是不可避免的——再优秀的程序员也会犯错,再严格的 Code Review 也有漏网之鱼。

行级安全(Row-Level Security, RLS) 是在数据库层面做权限控制。你定义一条规则:"用户只能看到自己的订单",数据库会自动过滤,无论从哪个入口查询都生效——API 接口、后台管理面板、数据迁移脚本,全部受约束。

这意味着即使代码有 bug,数据库也不会泄露其他用户的数据。RLS 就像银行金库的最后一道门——即使小偷突破了所有外围防线(代码层的权限检查),到了金库门前还是打不开(数据库层的 RLS)。

什么时候需要 RLS

如果你用 Supabase,RLS 几乎是必须的。 因为 Supabase 的客户端 SDK 直接连数据库(通过 PostgREST),前端代码可以直接发 SQL 查询。没有 RLS 的话,任何人都可以在浏览器控制台里修改查询条件,看到所有用户的数据——这等于裸奔。

如果你用 Neon + 自己的后端 API,RLS 不是必须的。 因为用户的请求先经过你的后端代码,你可以在 API 层做权限控制(比如"只返回当前登录用户的订单")。但 RLS 作为额外的安全层仍然值得考虑——多一道防线总比少一道好,特别是当你的应用处理敏感数据(支付信息、个人隐私)时。

审查 RLS 策略的要点

如果 AI 生成了 RLS 策略,检查三件事:

  • 是否真的启用了? 光定义策略不够,还要 ALTER TABLE ... ENABLE ROW LEVEL SECURITY,否则策略不生效——就像手机设了密码但没开启锁屏,密码形同虚设。这是最常见的遗漏——策略定义和启用是两个独立步骤,缺一不可
  • 策略覆盖全了吗? SELECT、INSERT、UPDATE、DELETE 各需要单独的策略,漏一个就是安全漏洞。比如你定义了"用户只能查看自己的订单"(SELECT 策略),但忘了定义 UPDATE 策略,那用户就能修改别人的订单
  • 会不会拖慢查询? RLS 策略里用到的列(比如 user_id)必须有索引,否则每次查询都要全表扫描来做权限过滤。这又回到了索引的重要性——没有索引的 RLS 策略,安全是安全了,但慢得让人受不了

数据安全基础

数据库里存的是你最宝贵的资产——用户数据。一旦泄露或损坏,后果可能是灾难性的。这一节讲三个最基本的安全措施。

SQL 注入:ORM 已经帮你挡住了

SQL 注入是最经典的安全漏洞,已经存在了二十多年,至今仍然是 Web 应用被攻击的主要方式之一。

它的原理很简单:攻击者在输入框里填入恶意的 SQL 片段,如果后端代码直接把用户输入拼接到 SQL 语句里,这些恶意片段就会被当作 SQL 执行。比如在登录框的"用户名"里输入 ' OR 1=1 --,如果代码没做防护,就能绕过密码验证登录任何账号。

好消息是:使用 ORM(Drizzle/Prisma)就自动防住了。ORM 内部使用参数化查询——用户输入的内容会被当作"数据"而不是"代码"处理,无论输入什么都不会被当作 SQL 执行。这就像把用户的输入装进一个密封的信封里交给数据库,数据库只会读信封里的内容,不会把它当作指令执行。

但如果审查时看到 AI 生成了手写 SQL(字符串拼接的那种,比如 `SELECT * FROM users WHERE name = '${userName}'`),要警惕——这种写法没有参数化保护,容易被 SQL 注入攻击。应该改成 ORM 的写法或参数化查询。

最小权限:别用管理员账号跑应用

这个道理很直觉:你不会把家里所有房间的钥匙都给快递员,只会给他大门的钥匙。你不会把银行卡密码告诉外卖小哥,只会付当次的餐费。数据库也一样。

应用连接数据库的账号,应该只有它需要的最小权限:能读数据、能写数据、能改数据,但不能删表、不能改表结构、不能创建新用户。管理员账号只在迁移和维护时使用。

为什么这很重要?因为应用代码是暴露在互联网上的,它是攻击者最容易接触到的入口。如果应用使用的数据库账号拥有管理员权限,一旦应用被攻破(比如通过某个未修复的漏洞),攻击者就能用这个账号做任何事——删除所有表、导出所有数据、甚至植入后门。

但如果应用账号只有读写权限,即使被攻破,攻击者也做不了太大的破坏——他能读到数据(这已经很糟了),但至少不能删表、不能改结构、不能影响数据库的正常运行。

备份是底线

数据是产品的灵魂。代码丢了可以重写,UI 丑了可以换皮,但如果用户数据丢了——用户的账号、订单、聊天记录、创作内容——你的产品就彻底完了。没有任何技术手段能从零恢复丢失的用户数据。

备份策略三要素:

  • 自动备份:Neon 和 Supabase 都提供自动备份,确认已开启。不要依赖手动备份——你一定会忘的
  • 多地备份:重要数据不要只存一个地方。云服务商的数据中心也可能出故障(虽然概率很低),鸡蛋不要放在一个篮子里
  • 恢复演练:这是最容易被忽视的一点。定期测试从备份恢复,确认备份真的能用。太多人做了备份但从没测试过,等到需要恢复时才发现备份文件损坏、格式不兼容、或者恢复流程根本跑不通。备份的价值不在于"有没有做",而在于"能不能恢复"
💡 本节核心要点
  • AI 交叉论证:让不同 AI 互相审查 Schema,2 轮迭代就能得到健壮设计
  • 索引是性能关键:外键列必须加索引,PostgreSQL 不会自动创建
  • RLS 防数据泄露:数据库层面的权限控制,即使代码有 bug 也不会泄露数据
  • 连接池防崩溃:生产环境必须用连接池,否则几十个用户就能打满连接
  • 备份是底线:自动备份 + 恢复演练,缺一不可
最后编辑:Alex 2026-03-05 11:39:51