凌晨三点,某游戏公司的运维被报警惊醒——用户表空了。黑客没用什么高级技术,只是在登录框里输入了一串字符,后端就直接执行了。这不是段子,是真实发生过的删库事件。今天聊的,就是怎么让数据库学会自我保护。
关系设计的三个基本款
![]()
关系型数据库的本质,就是一堆会互相找人的表格。搞不清关系,表结构迟早把自己压垮。
一对一(1:1)是最少见的。比如用户表和用户安全设置表——后者敏感、查询少,拆出来用user_id一对一挂着,既隔离风险又不影响主表性能。
一对多(1:N)是日常主力。公司和员工:一家公司对多人,但员工通常只认一家主公司。关键设计在"多"的那边加外键,比如employees表放company_id。这里有个隐藏武器叫级联删除(CASCADE DELETE),公司破产时自动清理关联员工,省得手忙脚乱。
多对多(M:N)最容易踩坑。学生和课程:你没法在学生表里塞多个class_id,也没法在课程表里塞多个student_id。解法必须建第三张表——连接表(Junction Table),只存student_id和class_id两列,纯粹当桥用。
很多人初期设计时怕表多,硬把多对多塞进某一边,结果查询时要么数据冗余爆炸,要么连表语句写到怀疑人生。该拆就拆,三张干净的小表胜过两张臃肿的大表。
参数化查询:堵上SQL注入的物理漏洞
回到开头那个删库事件。黑客输入的是:' OR 1=1; DROP TABLE users; --
如果后端用Python的f-string拼接SQL,代码长这样:
query = f"SELECT * FROM users WHERE email = '{user_input}';"
黑客的输入被原样塞进字符串,最终执行的其实是两句:先查邮箱,再删表。后面的单引号和注释符--把原语句的结尾吃掉,数据库乖乖照办。
参数化查询的解决思路很朴素:把"数据"和"指令"彻底分开。用户输入永远只被当作数据填充,不会被解析成SQL命令。具体实现因语言而异——Python用占位符%s或?,Java用PreparedStatement,Node用参数化驱动——原理都是预编译语句结构,再安全地填入值。
这不是"推荐做法",是底线。没做参数化的接口,等于在公网裸奔。
触发器:数据库里的自动法务
触发器(Trigger)是挂在表上的自动脚本,满足条件就执行,不需要应用层调用。
典型场景一:审计日志。用户表有更新?触发器自动往audit_log表插一条记录,记下谁、什么时候、改了什么。应用层完全无感知,想绕过都没门。
典型场景二:数据校验。插入订单时,触发器检查库存表,库存为零直接抛异常回滚。这比在应用层校验更底层,防的是有人绕过API直接操作数据库。
典型场景三:级联逻辑的复杂版。外键的级联删除是 blunt instrument(钝器),触发器可以做更精细的判断:删除客户前,先检查是否有未结订单,有则阻止删除并提示。
但触发器是双刃剑。逻辑藏在数据库里,调试困难;高频触发时性能开销明显;多个触发器嵌套容易形成"触发器地狱",排查时想死。建议只用于跨表一致性、审计等无法在上层妥善解决的场景,业务逻辑尽量留在应用层。
索引:快与慢的终极交易
索引让查询变快,但天下没有免费午餐。
读多写少的表,索引是神器。用户表按邮箱查登录信息?给email列建索引,查询从全表扫描变成B-Tree定位,百万级数据也能毫秒返回。
写密集的表,索引是负担。每次插入、更新、删除,数据库都要维护索引结构。日志表、流水表如果乱建索引,写入性能可能暴跌一个数量级。
复合索引(多列联合)的顺序有讲究。查询条件里最常用的过滤列放前面,范围查询的列放后面。顺序错了,索引可能部分失效或完全不被使用。
覆盖索引是高级技巧:索引包含查询所需的所有列,数据库不用回表查数据,直接返回索引内容。空间换时间,适合高频查询的固定字段组合。
最后说一个反直觉的点:索引不是越多越好。执行计划器面对过多索引时,选择成本本身会变成开销;更新时的写放大也会让系统抖动。定期用EXPLAIN分析慢查询,用慢日志定位问题,比盲目堆索引靠谱。
Day 10项目:审计追踪系统实战
原文档的实战项目是设计一个审计日志系统,要求记录谁在什么时间对哪张表做了什么操作。这是触发器的经典应用场景。
核心表结构:一张通用的audit_logs表,包含操作类型(INSERT/UPDATE/DELETE)、目标表名、目标记录ID、旧值快照、新值快照、操作人、时间戳。然后在需要审计的业务表上建触发器,捕获数据变化并写入日志。
设计要点:旧值和新值用JSON或TEXT存,保留完整上下文;操作人ID需要从应用层通过会话变量或专用字段传递,纯数据库层无法识别"当前用户是谁";日志表独立分区或定期归档,避免无限膨胀拖垮性能。
这个设计的隐藏价值在于合规。GDPR、等保2.0、SOX审计都要求数据变更可追溯,触发器实现的审计日志比应用层记录更难篡改,是监管认可的证据链。
怎么用起来
如果你是架构师: review现有系统,任何拼接SQL的地方立即排期改造;梳理核心表的关系设计,多对多关系有没有硬塞成一对多;审计日志有没有,是不是应用层写的(应用层日志可以被绕过或伪造)。
如果你是开发者:检查自己负责的接口,确认ORM或数据库驱动真的用了参数化,而不是表面封装实际拼接;写复杂查询时先用EXPLAIN看执行计划,确认索引生效;触发器只用于数据完整性约束和审计,别塞业务逻辑进去。
数据库是系统的最后一道防线。应用层可以重构、可以降级,数据库崩了就是真的崩了。参数化查询、合理的关系设计、克制的触发器使用——这三件事不花哨,但能保你半夜不被报警吵醒。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.