RAND()在单条INSERT中可能被多次求值,导致密码字段与实际授权值不一致;且无种子控制、不可复现,调试困难;应改用UUID_SHORT()等稳定函数构造可控随机串。

为什么不能用 RAND() 直接拼接密码字符串
直接写 CONCAT('pwd', FLOOR(RAND()*10000)) 看似简单,但 MySQL 的 RAND() 在一条 INSERT 语句中可能被多次求值(尤其配合 UUID() 或子查询时),导致生成的密码字段和实际授权使用的密码不一致。更严重的是,如果测试并发执行,RAND() 无种子控制,无法复现——调试失败用例时根本不知道当时用了哪个密码。
用 UUID_SHORT() + 截取构造可重现的随机串
UUID_SHORT() 每次调用返回唯一整数,且在单条 SQL 内稳定(不会像 RAND() 那样被重算)。适合做“伪随机但可控”的基础。关键不是完全随机,而是每次创建账号时能生成差异足够大、无规律、不重复的密码,同时便于日志追踪。
实操建议:
- 用
LPAD(HEX(UUID_SHORT()), 12, '0')得到 12 位十六进制字符串,长度固定、无特殊字符,兼容所有 MySQL 认证插件(包括caching_sha2_password) - 避免用
MD5()或SHA2()—— 它们输出含大小写字母+数字,但某些旧版客户端或连接池对特殊字符(如+、/)解析异常 - 不要截取
UUID()的部分 —— 其格式含短横线,MySQL 账号密码字段虽允许,但部分自动化工具(如 pytest-mysql)会因未转义而报语法错误
建账号必须显式指定 IDENTIFIED WITH 插件
MySQL 8.0 默认用 caching_sha2_password,但很多测试镜像(如 mysql:5.7 或某些 CI 容器)仍跑 5.7,认证插件是 mysql_native_password。如果建号时不声明,MySQL 会按 server 默认插件存密码哈希,但测试代码用 JDBC/PyMySQL 连接时若没配对插件,直接报 Authentication plugin 'caching_sha2_password' cannot be loaded。
安全且兼容的做法:
- 统一用
CREATE USER 'test_<suffix>'@'%' IDENTIFIED WITH mysql_native_password BY '<pwd>'—— 5.7 和 8.0 都支持该插件,且 PyMySQL、mysqlclient 默认适配 - 后缀推荐用
CONNECTION_ID()或@@PID(需先查),避免并发建号冲突;例如:CONCAT('test_', CONNECTION_ID()) - 务必加
@'%'而非@'localhost'—— 容器内测试常通过 bridge 网络访问 MySQL,localhost会走 socket,权限匹配失败
销毁账号前先 REVOKE 再 DROP,否则残留权限
仅执行 DROP USER 不保证权限立即失效。MySQL 权限缓存可能让已建立的连接继续持有旧权限,尤其在高并发测试中,后续用例可能误用前一个账号的残留权限,造成非预期通过或失败。
可靠清理步骤:
- 先
REVOKE ALL PRIVILEGES ON *.* FROM 'test_xxx'@'%'—— 显式清除内存中加载的权限 - 再
DROP USER 'test_xxx'@'%'—— 物理删除账号记录 - 最后可选
FLUSH PRIVILEGES,但仅在极少数修改了磁盘权限表(如直接改mysql.user)时才必要;自动化测试中通常不需要
真正容易被忽略的是:测试框架 teardown 阶段如果只写 DROP USER,又没捕获异常(比如账号已被删),整个清理就静默跳过——下次测试可能连上旧账号,密码还是上次的,结果不可控。


















