MySQL不支持用户级内存限制,须双管齐下:账号侧用MAX_USER_CONNECTIONS防连接泄露,系统侧用cgroup v2(如systemd MemoryMax)设mysqld进程硬内存上限。

MySQL 原生不支持按用户限制内存使用量,所有“用户级内存限制”都是伪命题;真要防宕机,必须分两层下手:账号侧用 MAX_USER_CONNECTIONS 切断连接泄露源头,系统侧用 cgroup 给整个 mysqld 进程设硬内存上限。
为什么不能直接给 MySQL 用户配内存 quota
MySQL 没有 max_memory_per_user 这类参数。每个连接会独占一份 sort_buffer_size、join_buffer_size 等会话级缓冲区,但这些值由客户端登录后自行 SET(除非你禁掉 SET 权限),且只在当前连接生效——你改了全局变量,旧连接仍用旧值。更关键的是:innodb_buffer_pool_size 是全局共享的,所有用户共用同一块内存池,根本无法隔离。
所以所谓“限制用户内存”,本质是两种思路:
- 堵住连接数 → 防止大量并发连接把会话缓冲区堆爆
- 锁死 mysqld 整体内存上限 → 防止 InnoDB 缓冲池 + 所有连接缓冲 + OS 开销一起吃光物理内存
账号侧必须配 MAX_USER_CONNECTIONS
这是唯一能立即见效、精准隔离单个账号风险的手段。它限制的是该账号**同时存活**的连接数,不是每小时新建数,也不是总连接次数。
常见错误和实操要点:
- 用
ALTER USER 'user'@'host' WITH MAX_USER_CONNECTIONS N修改已有账号,不要用GRANT ... WITH MAX_USER_CONNECTIONS(8.0+ 已废弃且语义歧义) - 若用户有多个
host(如'app'@'192.168.1.%'和'app'@'localhost'),必须分别执行ALTER USER,它们是独立账户 - 设为
0表示不限制(退回到全局max_connections),生产环境严禁留0 - 验证是否生效:查
SELECT User, Host, Max_user_connections FROM mysql.user WHERE User = 'xxx';,再用SELECT user, COUNT(*) FROM performance_schema.threads WHERE TYPE = 'FOREGROUND' AND user = 'xxx' GROUP BY user;看实时连接数
系统侧必须用 cgroup v2 限制 mysqld 总内存
仅靠 MySQL 配置参数(如调小 innodb_buffer_pool_size)不可靠:InnoDB 会动态申请内存,OS 也可能因 swap 或 page cache 导致实际占用远超配置值。真正硬限必须落到操作系统层。
推荐用 systemd 方式(最稳,无需手动维护 cgroup 目录):
- 编辑 MySQL service 文件:
sudo systemctl edit mysqld - 写入:
[Service] MemoryMax=4G MemoryHigh=3.5G
sudo systemctl daemon-reload && sudo systemctl restart mysqld
注意:MemoryMax 是硬限,超了 mysqld 会被 OOM killer 杀;MemoryHigh 是软限,仅触发内核回收,不能防宕机。
别碰那些看似有用实则误导的配置
以下操作对防内存型宕机基本无效,还可能引发新问题:
- 调小
sort_buffer_size或join_buffer_size:默认值本就不高(256K~4M),设太小反而让查询频繁落盘,IO 拉满拖垮整体性能 - 依赖
RESOURCE GROUP:只对SELECT生效,且需显式SET RESOURCE GROUP,应用不配合就等于没开 - 设
tmp_table_size/max_heap_table_size:只能控制单次查询的内存临时表大小,无法约束总内存占用 - 用
MAX_QUERIES_PER_HOUR类频次限制:防的是请求密度,不是内存消耗;一个慢查询照样吃光内存
真正容易被忽略的点是:账号连接数限制和系统级 cgroup 必须同时存在。只做前者,遇到大缓冲池 + 少量恶意连接仍可能 OOM;只做后者,一旦某个账号连接泄露,mysqld 会先耗尽自身配额再被杀,期间其他账号全不可用。两者缺一不可。


















