mysql 8.0.19+原生支持max_queries_per_hour,仅限select语句、按自然小时(整点重置)计数;mysql 5.7需依赖定时alter user重置max_questions,或使用proxysql等外部工具实现可靠限流。

MySQL 5.7 及以上版本原生不支持按小时粒度限制查询次数,必须通过 max_questions 配合账户重置周期或外部工具实现“每小时”效果;MySQL 8.0.19+ 的 CREATE USER ... WITH MAX_QUERIES_PER_HOUR 是唯一原生支持,但注意它只统计 SELECT,且重置是按自然小时而非滑动窗口。
MySQL 8.0.19+:用 MAX_QUERIES_PER_HOUR 实现自然小时限制
这是最接近需求的原生方案,但行为有隐含前提:
-
MAX_QUERIES_PER_HOUR仅限制SELECT语句,INSERT/UPDATE/DELETE不计入 - 计数器在每小时整点(如 14:00、15:00)自动清零,不是从用户首次查询起算 60 分钟滑动窗口
- 必须用
CREATE USER或ALTER USER显式设置,GRANT不生效
示例:
CREATE USER 'report_user'@'%' IDENTIFIED BY 'pwd123'; ALTER USER 'report_user'@'%' WITH MAX_QUERIES_PER_HOUR 300;
若用户在 14:59 执行第 300 次 SELECT,15:00 后立即可再执行 300 次;但若在 14:01 执行第 1 次,14:59 就已达上限,需等到 15:00 才恢复。
MySQL 5.7 或低版本:max_questions + 定时重置是唯一可行路径
max_questions 是全局配额(生命周期内总次数),无法直接表达“每小时”。常见误操作是设一个很小的值(如 100),指望靠定期 FLUSH PRIVILEGES 重置——但 max_questions 计数器不会因权限刷新而归零,只能通过 ALTER USER ... WITH MAX_QUERIES_PER_HOUR 0(即禁用)再设新值来间接“重置”,且该操作本身会重置计数器。
- 真正可行的做法:写一个定时脚本(如 Linux cron),每小时执行一次
ALTER USER 'u'@'h' WITH MAX_QUERIES_PER_HOUR N - 注意:每次
ALTER USER ... WITH都会重置当前计数器,所以必须保证脚本严格按时运行,否则可能造成配额提前耗尽或未及时恢复 - 该方式在主从架构下存在延迟风险——从库权限表同步滞后会导致限流不一致
为什么不能依赖应用层限流?
应用层(如 Python/Java 连接池中计数)看似灵活,但实际不可靠:
- 连接复用(如
mysql-connector-python的pool_size)导致多个请求共享同一连接,计数归属混乱 - 连接断开重连后计数丢失,除非引入 Redis 等外部存储,但这已脱离 MySQL 原生能力范畴
- 无法拦截直接通过命令行、DBeaver 等工具连接的查询,安全边界失效
换言之,真要防滥用,必须在数据库入口处控制,而不是在应用逻辑里“尽力而为”。
容易被忽略的细节:权限变更与监控验证
设置了限流后,用户触发上限时不会报错,而是静默拒绝后续 SELECT,返回错误:ERROR 1226 (42000): User 'xxx' has exceeded the 'max_questions' resource (current value: N)。但这个错误只有在用户真正执行查询时才出现,日常 SHOW GRANTS 查不到当前已用次数。
- 检查当前配额:用
SELECT user, host, max_questions FROM mysql.user WHERE user = 'xxx'; - 重置计数器:执行任意
ALTER USER ... WITH MAX_QUERIES_PER_HOUR X(X 可与当前值相同),即可清零 -
max_questions对所有语句类型都计数(包括SHOW、DESCRIBE),而MAX_QUERIES_PER_HOUR只计SELECT——这点在审计日志分析时极易混淆
如果你的应用混合使用多种语句且需要统一限流,max_questions 更贴近需求,但必须接受它没有时间维度的事实。











