用户表设计要避开 TEXT
存储基础字段
小型社交平台的用户表常误用
TEXT类型存昵称、个性签名等——这会拖慢查询、影响索引效率,且 MySQL 对
TEXT字段的排序和分组限制多。实际只需:昵称用
VARCHAR(32)(足够覆盖中文 16 字+英文),个性签名用
VARCHAR(255);头像路径、个人简介这类可变长但通常不参与 WHERE/ORDER BY 的字段,才考虑
TEXT。
关键点:
id必须是
BIGINT UNSIGNED AUTO_INCREMENT,避免未来用户量破千万后溢出
phone和
NULL(比如只用微信登录的用户可能没填邮箱)
password_hash固定长度,用
CHAR(60)(bcrypt 输出恒为 60 字符),别用
VARCHAR状态字段如
status推荐用
TINYINT UNSIGNED(0=禁用,1=正常,2=待验证),比字符串枚举更省内存、更快比较
密码不能明文存,但别自己实现加盐逻辑
看到有人用
MD5(CONCAT(password, salt))就直接上线,这是危险信号。MySQL 内置函数如
SHA2()或
MD5()是纯计算型,没有自适应迭代、无法抵抗 GPU 暴力破解。
正确做法是:在应用层(Python/Node.js/PHP)用标准库生成 bcrypt 或 Argon2 哈希,再存进数据库。例如 Python 的
bcrypt.hashpw()输出自带盐值和参数,一条记录里全包含,数据库只负责安全存储,不参与加密过程。
常见错误:
把盐值单独存在另一张表或字段里——增加查询开销,且盐值泄露也不影响主哈希安全性 用UUID()当盐——它不是密码学安全随机数,且每次调用都不同,导致无法验证 在 SQL 里写
INSERT ... VALUES (..., SHA2(CONCAT(@pwd, RAND()), 256))——
RAND()每次执行结果不同,登录时根本没法复现
头像和第三方登录字段要预留扩展性
初期只支持手机号注册,但两周后就上了微信登录——如果当初
user表没留
third_party_id和
provider字段,就得加字段、锁表、改所有 INSERT 逻辑。
建议一步到位:
avatar_url用
VARCHAR(512),存 CDN 地址(如
https://www.herecours.com/d/file/efpub/2026/28-28/20260228123225722279.jpg),别存本地路径或二进制 blob
provider用
ENUM('phone', 'wechat', 'github', 'apple') 或更灵活的 VARCHAR(20),避免 ALTER TABLE 加新渠道
third_party_id用
VARCHAR(255),微信 OpenID、GitHub ID、Apple Subject 长度差异大,
CHAR(32)不够用 加联合唯一索引:
UNIQUE KEY uk_provider_uid (provider, third_party_id),防止同一微信用户重复绑定
查询活跃用户时别直接 SELECT *
扫全表
运营要看“最近 7 天登录过的用户”,如果写
SELECT * FROM user WHERE last_login_at > DATE_SUB(NOW(), INTERVAL 7 DAY),而
last_login_at没索引,就会全表扫描——哪怕只有 10 万用户,延迟也可能飙到秒级。
必须做的两件事:
给last_login_at加普通索引:
ALTER TABLE user ADD INDEX idx_last_login (last_login_at)查活跃用户列表时,只 SELECT 必需字段,比如:
SELECT id, nickname, avatar_url, last_login_at FROM user WHERE ...,别带
password_hash或
bio这类大字段 如果还要按“粉丝数”排序,别临时
JOIN粉丝表算总数——提前用定时任务或触发器把
follower_count缓存在用户表里
真实瓶颈往往不在数据量大小,而在字段冗余和索引缺失。一张 5 万行的
user表,只要三个字段加了索引、两个大字段拆出去,QPS 就能从 50 跳到 800。
