MAX_QUERIES_PER_HOUR 在 MySQL 5.7+ 默认不生效,因服务端跳过 mysql.user 表的 max_questions 检查;MySQL 8.0 彻底移除运行时校验,需用 ProxySQL 或应用层(Redis+Lua/RateLimiter)实现可靠限流。

MySQL 5.7+ 中 MAX_QUERIES_PER_HOUR 实际上不生效?
直接说结论:MAX_QUERIES_PER_HOUR 在 MySQL 5.7 及以上默认关闭资源限制,即使你显式设置了,也不会触发限流。原因在于服务端变量 old_passwords 已废弃,而更关键的是:max_questions(对应 MAX_QUERIES_PER_HOUR)依赖于 --old-passwords=0 和启用 mysql.user 表的 max_questions 字段校验逻辑——但 MySQL 5.7+ 默认跳过该字段检查。
验证方式:创建用户并设限后,用该用户持续执行 SELECT 1,观察是否报错 ERROR 1226 (42000): User 'u1' has exceeded the 'max_questions' resource。大概率不会出现。
- MySQL 5.7.6+ 默认禁用所有基于
mysql.user表的资源限制(MAX_QUERIES_PER_HOUR、MAX_UPDATES_PER_HOUR等) - 只有在启动时加参数
--old-passwords=0 --init-file=/path/to/enable_resource_limits.sql并手动启用相关逻辑,才可能恢复,但官方不推荐 - MySQL 8.0 彻底移除了对这些字段的运行时检查,
CREATE USER ... MAX_QUERIES_PER_HOUR N语法虽保留,但纯属兼容占位
替代方案:用 ProxySQL 实现每小时查询数硬限制
真正可控、生产可用的每小时查询频控,得靠中间件。ProxySQL 是目前最轻量且适配 MySQL 生态的选项,它支持基于 username + time window 的 query count 限流。
操作步骤:
- 在
mysql_users表中为用户配置max_connections和default_hostgroup(基础接入) - 在
mysql_query_rules中添加规则,匹配用户名 + SQL 类型(如SELECT),设置apply=1和sticky_conn=0 - 关键一步:启用
stats_mysql_query_digest并配合定时任务或外部脚本,每小时清空统计或告警超限;更稳妥的是用 ProxySQL 的mysql_query_rules_fast_routing+ 自定义match_digest+cache_ttl模拟窗口计数(需搭配 Lua 脚本) - 或者直接使用 ProxySQL v2.4+ 的
mysql_query_rules新字段limit_to_hostgroup和apply配合stats_mysql_commands_counters做聚合判断(需自行轮询 + kill session)
简单示例(ProxySQL CLI):
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
INSERT INTO mysql_query_rules (rule_id,username,destination_hostgroup,active,retries,match_digest,cache_ttl) VALUES (101,'app_user',1,1,3,'^SELECT ',3600000);
注意:这本身不是限流,只是缓存策略;真限流必须读 stats_mysql_query_digest 表按 username 和 hour(FROM_UNIXTIME(time_ms/1000)) 分组统计,再调 DELETE FROM mysql_sessions WHERE user='app_user' 或发 KILL 命令。
应用层自建限流:为什么比数据库原生更可靠
在业务代码里控制每小时查询次数,反而更稳定、可观察、易调试。数据库层限流难排查、无日志上下文、无法区分“有效查询”和“探测心跳”,而应用层可以绑定用户 ID、租户 ID、API 路径做多维限制。
- 推荐用 Redis + Lua 原子计数:
INCR+EXPIRE设置 1 小时 TTL,GET判断是否超限 - 避免用
SETNX+ 单独EXPIRE,存在竞态(set 成功但 expire 失败) - 如果用 Go,可直接上
golang.org/x/time/rate的Limiter,但注意它是滑动窗口,不是固定小时窗口;要严格按自然小时限流,得用time.Now().Truncate(time.Hour)作为 key - Java 可用
Bucket4j配置Refill.greedy(60, Duration.ofHours(1)),底层仍建议存到 Redis 防多实例冲突
容易被忽略的关键点:限流 ≠ 防攻击,也不解决慢查询
MAX_QUERIES_PER_HOUR 类机制只管数量,不管单次耗时。一个 SELECT * FROM huge_table ORDER BY RAND() LIMIT 1 可能 1 小时只跑 1 次,却拖垮整个库。
- 真正要防滥用,得结合
max_execution_time(MySQL 5.7.8+)强制中断长查询 - 配合
pt-kill定期杀掉Command=Query且Time > 30的连接 - 权限最小化:给应用用户只授予具体表的
SELECT,禁用SHOW DATABASES、PROCESS等高危权限,比限流更治本 - 监控比限流重要:用
performance_schema.events_statements_summary_by_user_by_event_name查每个用户的COUNT_STAR和SUM_TIMER_WAIT,早发现异常模式
别把限流当银弹。查多少次不重要,关键是谁在查、查什么、花了多久。










