加密对索引的影响:字段加密后的查询难点
加密审计日志,说白了就是把日志里敏感字段加密后再存。
想法很美好——查的时候解密,存的时候安全。
但我干了这么多年数据库优化,见过太多团队一加密就卡死。
为什么?因为字段一旦加密,索引的作用就大打折扣。
密码学决定了索引“看不见”原文
最基础的问题:索引建立在原始值上。
你建一个B+树索引,里面存的是一堆比较大小、排序后的“原文”。
字段加密后,索引里存的是密文。
那查询时怎么办?
比如你要查 email = 'alice@example.com',数据库得把密文拿出来跟索引里的密文比。
听起来没毛病?
问题在于——大多数加密算法是非确定性的。
同一原文加密两次,两次密文不一样。
索引就废了:根本没法用等值匹配。
核心冲突:索引需要确定性(相同值相同结果),加密追求随机性(防频率分析)。
我前两年帮一家金融公司做审计日志改造,他们用AES-GCM加密用户手机号。一上线,按手机号查日志的查询从50ms爆到12秒。
排查下来,索引完全失效——每个手机号每次加密都带不同初始向量。
确定性加密:救星还是半吊子?
那有没有办法?有——确定性加密。
比如AES在ECB模式下,或者用固定初始向量的AES-CBC。
同一原文,密文永远一样。
这样一来,B+树索引能用了。等值查询 WHERE email = 'xxx' 走索引没问题。
但是,坑在后面。
| 加密类型 | 索引支持 | 典型场景 | 安全风险 |
|---|---|---|---|
| 非确定性加密(AES-GCM) | 不支持任何索引 | 高安全需求字段 | 低(随机化) |
| 确定性加密(AES-ECB) | 支持等值索引 | 需要等值查询的敏感字段 | 中(频率泄露) |
我自己做过一个实验:用确定性加密后,电话号段(前三位)相同的用户,密文前几字节也完全相同。
攻击者如果拿到密文,光看重复度就能猜出号段分布。
注意:确定性加密虽然救了索引,但密码学上说等于脱了裤子跑。——频率攻击可以把密文映射回原文。审计日志场景下,如果攻击者能访问密文,这就是个隐患。
范围查询:索引的阿克琉斯之踵
等值查询勉强能救。范围查询呢?
WHERE created_at BETWEEN '2025-01-01' AND '2025-01-31'
这类查询依赖索引的排序能力。B+树天生支持范围扫描——但前提是索引字段的排序顺序和查询条件一致。
加密后,密文字节顺序和原文顺序完全无关。
你能想象吗?
数字“10”的密文可能比“2”的密文在二进制上更大,也可能更小,完全随机。
所以B+树无法做范围修剪,只能全表扫描。
我在一个支付日志系统里见过类似问题:时间戳用AES加密后,按时间范围查交易记录,每次都要拉几千万行到应用层解密再过滤。——那种体验,运维同事直接骂娘。
缓兵之计:把加密字段拆成两列——一列存密文(用于安全),一列存哈希(如SHA256,用于等值索引)。但要记住,哈希也不支持范围查询。
部分匹配查询:LIKE的绝症
再聊聊 LIKE '%xxx%'。
普通字段可以建前缀索引或全文索引。加密后呢?
密文看起来是乱码。
你不可能从密文中“抠”出一部分去匹配原文里的子串。
即使你用确定性加密,密文块大小固定(比如16字节),子串边界早被打乱了。
换句话说:一旦字段加密,LIKE查询必然走到全表扫描+逐行解密匹配。
| 查询类型 | 未加密 | 确定性加密 | 非确定性加密 |
|---|---|---|---|
| 等值 | ✓ 走索引 | ✓ 走索引(频率泄露) | ✗ 全表扫描 |
| 范围 | ✓ 走索引 | ✗ 全表扫描 | ✗ 全表扫描 |
| LIKE | ✓ 部分索引 | ✗ 全表扫描 | ✗ 全表扫描 |
| 排序 | ✓ 走索引 | ✗ 需要在应用层排序 | ✗ 需要在应用层排序 |
一张图看懂加密后索引能不能用
下面这张流程图给你一个决策框架,我自己做审计日志设计时经常贴在显眼位置。
避坑指南:我曾经踩过的两个雷
- 第一个雷:把加密字段设为主键。 有一次,我接手一个遗留项目,它用UUID的MD5哈希作为主键——其实是想隐藏业务含义。但后来需要按创建时间排序,主键毫无意义,索引重建时锁表锁了半小时。现在我的习惯:主键永远用自增整数或有序UUID,加密字段只能做辅助索引。
- 第二个雷:忘了哈希前缀也能做部分匹配。 之前有个需求:模糊匹配身份证后四位。我本想放弃加密直接用明文,后来灵机一动——单独存一个“后四位哈希+盐”的列,建立普通索引。查的时候算一次哈希,走索引秒回。虽然不能查前四位,但对业务够用了。算是“曲线救国”。
核心心得:加密字段的查询设计,本质是在安全与性能之间画一条自己的容忍线。不要试图在所有查询上都保持全功能索引——那没戏。提前分析业务最频繁的查询类型,只保那几类,其他的走全表扫描或离线分析。