Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › cgroups

How to Stop MySQL Being Killed by the Out-of-Memory Killer

By Freedom251 Team · · 11 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Stop recurring MySQL OOM kills by first identifying which memory limit was exhausted, then making MySQL’s peak usage fit inside that limit with headroom. The limit may be the whole host, a container or cgroup, or a systemd-oomd policy. Start by measuring the kill and the real peak resident memory; then size innodb_buffer_pool_size, connection concurrency, and per-thread buffers as one budget. Reducing the buffer pool alone will not help if connection spikes, temporary tables, or a cgroup limit consume the remaining memory.

What “out of memory” means for a MySQL service

Linux can decide that memory is exhausted at more than one scope. A bare-metal host may run out of reclaimable RAM and swap, while a container can hit its cgroup limit even when the host has plenty of free memory. A systemd unit may also be selected by systemd-oomd before the kernel’s OOM killer runs. The same symptom—mysqld disappearing—therefore needs different fixes.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Host-wide kernel OOM: the kernel cannot satisfy allocations for the system as a whole and chooses a process to kill.
  • Container or cgroup OOM: the memory controller reaches the unit or container’s memory.max; host RAM outside that scope does not count.
  • systemd-oomd action: a userspace service watches cgroups-v2 and pressure stall information (PSI), then kills an eligible unit according to its policy.

Linux documents that the amount of “allowed” memory depends on the context in which the OOM killer was called. Establishing that context is the first step; changing a MySQL variable before doing so can leave the actual limit untouched.

1. Confirm who killed MySQL

Check kernel messages

Run these commands as an administrator immediately after an incident:

journalctl -k -b | grep -Ei 'out of memory|oom|oom-kill|killed process'
dmesg -T | grep -Ei 'out of memory|oom|killed process'

dmesg may be restricted or unavailable on hardened systems, so the kernel journal is the preferred source. Look for the killed PID, the process name, the reported total and available memory, and whether the message names a cgroup or memory controller. Save the complete event rather than only the final “Killed process” line; the preceding lines often identify the allocation context and current swap state.

Read MySQL’s error log

Find the configured error-log path and inspect entries around the same timestamp. A clean restart with no InnoDB shutdown sequence, followed by a kernel “Killed process mysqld” line, strongly indicates an external kill rather than a MySQL-initiated shutdown. The error log can also show startup recovery, which tells you whether the previous termination was abrupt.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
mysql -NBe "SHOW VARIABLES LIKE 'log_error';"
# Then inspect the reported file, for example:
sudo less /path/from/log_error

Correlate the PID and UTC/local timestamps across the kernel journal, the MySQL log, and the service manager. Do not infer an OOM event solely from a missing process: a crash, manual stop, watchdog timeout, or storage failure can look similar from the application side.

Check for systemd-oomd

systemctl status systemd-oomd
journalctl -u systemd-oomd --since "-2 hours"
systemctl show mysql.service -p ManagedOOMSwap -p ManagedOOMMemoryPressure -p ControlGroup

systemd-oomd uses cgroups-v2 and PSI to take corrective action before a kernel-space OOM. If its journal names the MySQL unit, treat that as the cause even when the kernel log contains no “Out of memory” event. A unit configured with ManagedOOMSwap= or ManagedOOMMemoryPressure= can be selected when its policy threshold is met.

2. Identify the memory scope and its hard limit

Host-wide memory

Capture a time-consistent snapshot of RAM, swap, and the largest processes:

free -h
swapon --show
ps -eo pid,comm,rss,%mem --sort=-rss | head -n 20
cat /proc/meminfo

Use peak observations, not an idle reading taken after traffic has stopped. Record the machine’s physical RAM, configured swap, other services, filesystem-cache pressure, and the time of the highest connection count.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Container and cgroup limits

A cgroup can impose a lower limit than physical memory. For a systemd service, obtain its cgroup path:

systemctl show mysql.service -p ControlGroup -p MemoryMax -p MemoryHigh
systemctl status mysql.service

On a cgroups-v2 host, inspect the corresponding directory under /sys/fs/cgroup:

cat /sys/fs/cgroup/<unit-path>/memory.max
cat /sys/fs/cgroup/<unit-path>/memory.current
cat /sys/fs/cgroup/<unit-path>/memory.events

Replace <unit-path> with the path returned by ControlGroup; do not assume a distribution-specific directory. A numeric memory.max is bytes; the value max means no finite limit at that cgroup level. In a container, inspect the runtime’s memory setting as well. Increasing host RAM does not override a lower memory.max.

Inspect systemd drop-ins and policy

systemctl cat mysql.service
systemctl show mysql.service -p MemoryMax -p MemoryHigh -p ManagedOOMSwap -p ManagedOOMMemoryPressure

Drop-in files may set a limit that is not visible in the vendor unit file. A low MemoryHigh can create sustained reclaim and PSI pressure; MemoryMax is the hard ceiling. Change these deliberately and document the infrastructure impact.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Measure MySQL’s real memory demand

Record the variables that form the budget

mysql -e "SHOW VARIABLES WHERE Variable_name IN (
'innodb_buffer_pool_size','max_connections','sort_buffer_size',
'read_buffer_size','read_rnd_buffer_size','join_buffer_size',
'tmp_table_size','max_heap_table_size','key_buffer_size',
'binlog_cache_size','binlog_stmt_cache_size','thread_stack',
'thread_cache_size','table_open_cache','performance_schema');"

Also record the current and peak connections:

mysql -e "SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected','Threads_running','Max_used_connections','Created_tmp_tables','Created_tmp_disk_tables');"

Threads_connected is a point-in-time value; Max_used_connections shows the highest simultaneous connection count since the server started or status was reset. Use an interval monitor or your metrics system to capture peaks during the incident window. A low idle RSS does not prove that the configured peak is safe.

Understand global versus per-thread allocations

innodb_buffer_pool_size, key_buffer_size, and several caches are global allocations. Other buffers are allocated for sessions or operations and can multiply with concurrency. Sort, read, read-random, and join buffers may be created per connection; temporary tables can grow in memory up to the configured limits before spilling to disk. Thread stacks and client-library allocations add overhead that is easy to miss in a simple variable sum.

Rank #3
Sale
MySQL Cookbook
  • Used Book in Good Condition

Oracle-provided MySQL binaries commonly use a 256 KB thread stack, while a 2 MB stack is also common. Check your actual thread_stack value rather than assuming either figure. Performance Schema, replication workers, binary-log caches, table metadata, prepared statements, and allocator fragmentation add usage beyond the main buffers.

4. Build a conservative memory budget

Use MySQL’s planning estimate as a floor

MySQL’s startup guidance gives this illustrative estimate:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
innodb_buffer_pool_size + key_buffer_size
+ max_connections * (sort_buffer_size + read_buffer_size + binlog_cache_size)
+ max_connections * 2MB

It is a lower-bound planning aid, not a promise of a hard maximum. Add room for read_rnd_buffer_size, join_buffer_size, temporary-table growth, thread stacks as configured, Performance Schema, replication, client libraries, filesystem cache, the operating system, and every co-resident service. The formula’s 2 MB term is an illustrative allowance; it is not a substitute for measuring your build and workload.

Choose the buffer pool within the actual limit

MySQL documents a typical starting range of 50%–75% of system memory for innodb_buffer_pool_size. That range assumes the rest of the machine has been accounted for. On a shared host, a container with a strict limit, or a server with many connections, a lower percentage is safer. Calculate the percentage from the applicable cgroup limit when one exists, not from the host’s installed RAM.

Reserve explicit headroom for the OS and other services, then leave additional margin for traffic bursts. If the calculated total is close to the limit, reduce it before the next incident; memory pressure and allocator overhead are not predictable enough to run at the ceiling.

Account for concurrency, not just configuration

A high max_connections is a capacity promise. It permits many sessions to allocate per-thread buffers simultaneously, even if normal traffic uses fewer connections. Compare it with Max_used_connections and application pool settings. If the application opens short-lived bursts, queue work in a connection pool and set max_connections to a measured requirement plus a small operational margin rather than an arbitrary large number.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

5. Changes that usually stop the kills

Reduce the InnoDB buffer pool with measured headroom

Lowering innodb_buffer_pool_size reduces the largest predictable allocation on an InnoDB server. Modern MySQL supports resizing it while running; verify your version and monitor the resize operation. A smaller pool can increase disk reads and query latency, so watch buffer-pool hit behavior, I/O latency, and throughput after the change.

SET GLOBAL innodb_buffer_pool_size = 8589934592;

The value above is 8 GiB in bytes; choose a value from your budget, not this example. Persist the setting in the server configuration after validating it, because a dynamic change may not survive a restart. If your version or packaging does not support dynamic resizing, schedule a controlled restart instead of editing a live value and assuming it took effect.

Cap connections and fix the pool

Lowering max_connections limits the worst-case multiplication of per-session memory. It can expose application queuing or “too many connections” errors, so coordinate the value with every application pool and background job.

SET GLOBAL max_connections = 300;

Existing sessions are not forcibly disconnected by lowering the variable; the lower ceiling applies as new clients connect. Drain or recycle excess sessions through the application pool, then verify that peak demand remains below the new limit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Trim oversized per-thread and temporary-table buffers

Defaults are often safer than copied tuning recipes that set megabyte-sized buffers globally. Reduce unusually large sort_buffer_size, read_buffer_size, read_rnd_buffer_size, and join_buffer_size values first. Review tmp_table_size and max_heap_table_size together: the lower effective limit controls in-memory temporary tables, while large values let many concurrent sessions consume substantial RAM.

Changing these variables can alter sort speed, join behavior, and disk spill frequency. Test representative queries and monitor Created_tmp_disk_tables rather than raising every buffer to avoid a single slow query.

Review non-InnoDB allocations

If the server still approaches its limit after the main changes, inspect key_buffer_size for any MyISAM workload, binary-log and statement cache settings, table and thread caches, replication workers, and Performance Schema consumers. Disable or reduce features only when you understand their operational purpose; an unplanned reduction can trade an OOM kill for a replication or observability failure.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

6. Fix the limit when MySQL is in a container or systemd unit

Choose between raising the limit and reducing MySQL

Option Peak-memory effect Performance effect Restart requirement Blast radius and reversibility
Reduce buffer pool Directly lowers a large global allocation May increase reads and latency Often dynamic; verify version Contained to MySQL and easy to reverse
Lower max_connections Caps per-session multiplication Can queue or reject excess clients Usually dynamic May require application-pool changes; reversible
Reduce per-thread/temporary buffers Lowers potential burst usage More disk spills or slower individual operations Depends on variable Contained to query behavior; reversible
Raise cgroup or unit limit Adds available capacity without changing MySQL Usually preserves query performance Depends on deployment tooling Consumes host/infrastructure headroom and may raise cost
Change OOM score None None directly No, but policy reload may be needed Moves failure risk to another process; reversible but risky

If the cgroup limit is plainly below the memory needed for the workload, either raise it with a corresponding host-capacity check or reduce MySQL’s budget to fit. Do not assume a larger host instance helps while the container’s memory.max remains unchanged.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Apply a systemd limit change deliberately

Create a service drop-in using your distribution’s normal administration process, set a documented MemoryMax or MemoryHigh, then reload and restart only if required by the change. Confirm the effective value with systemctl show; a drop-in can be overridden by another layer. Raising a limit without reserving memory for neighboring services can simply move the next OOM to a different process.

7. Swap: useful pressure relief, not a cure

Swap can give the kernel time to reclaim cold pages and avoid an immediate kill, but it does not reduce MySQL’s allocation. Heavy swapping introduces latency and can make PSI-based policies more likely to act. Verify that swap exists and is sized for your operational policy:

swapon --show
free -h
cat /proc/sys/vm/swappiness

Monitor swap-in/out rates and query latency after any change. A system with no swap may need a controlled swap addition, while a latency-sensitive database may intentionally use limited swap. Treat the setting as part of capacity planning, not as permission to fill all RAM with MySQL.

8. Why common fixes fail

“I lowered the buffer pool and it still died”

  • The kill may be from a cgroup ceiling, not host RAM.
  • max_connections and per-thread buffers may dominate during a connection spike.
  • Temporary tables, replication, Performance Schema, or another service may consume the released memory.
  • The edited configuration may not have been loaded, or a dynamic change may have reverted after restart.
  • systemd-oomd may be acting on pressure policy rather than a kernel OOM.

Re-run the scope checks, verify effective variables with SHOW VARIABLES, and compare peak RSS with memory.current and the incident timestamp.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

“I raised max_connections because clients were rejected”

This can turn a visible capacity error into an OOM kill. First measure Max_used_connections, inspect pool behavior, and identify why sessions remain open. Increase capacity only when the resulting worst-case memory budget fits the applicable limit.

“I changed oom_score_adj to protect mysqld”

Linux uses oom_score_adj to adjust a process’s badness score when selecting a victim. A lower score can make mysqld less likely to be selected, but it does not lower MySQL’s allocation or create memory. The kernel may kill a monitoring agent, proxy, or other critical service instead. Use this only as a last-resort failure-priority decision after capacity and concurrency are fixed, and document which process becomes expendable.

9. A repeatable remediation procedure

  1. Preserve evidence: save kernel and systemd-oomd journal entries, the MySQL error log, killed PID, timestamp, RSS, swap state, and cgroup path.
  2. Classify the scope: determine whether host memory, memory.max, MemoryHigh/MemoryMax, or systemd-oomd policy was decisive.
  3. Capture the budget: record the MySQL variables, Max_used_connections, peak RSS, and other large processes.
  4. Set a safety target: reserve memory for the OS, filesystem cache, co-resident services, temporary growth, and a burst margin.
  5. Reduce the largest controllable risks: lower the buffer pool if necessary, cap connections, and trim oversized per-thread and temporary-table buffers.
  6. Change infrastructure limits only with capacity: raise the cgroup or unit limit when the host can safely provide it; otherwise keep MySQL within the existing limit.
  7. Validate under load: watch RSS, cgroup usage, PSI, swap activity, connection peaks, query latency, and disk temporary tables during representative traffic.
  8. Persist and document: place validated settings in the configuration or deployment definition, note restart requirements, and record rollback values.

10. Ongoing monitoring to prevent a recurrence

  • Alert on cgroup memory.current approaching memory.max, not only on host free memory.
  • Track MySQL RSS and peak RSS alongside Max_used_connections and Threads_running.
  • Graph swap-in/out, PSI memory pressure, and systemd-oomd actions.
  • Track temporary-table disk spills, query latency, and storage I/O after reducing buffers.
  • Review memory settings after schema changes, enabling replication, adding plugins, or colocating another service.

A healthy margin is one that remains after a realistic connection burst and temporary-table workload, not merely after an idle restart. Recalculate the budget whenever the workload or deployment limit changes.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cgroupsDatabase administrationLinuxMySQLout of memorysystemd

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.