Updated on 2026-08-18 GMT+08:00

OOM Exceptions

Scenarios

Out of memory (OOM) exceptions occur when the physical memory of a Linux system is fully occupied and cannot be released sufficiently through the memory reclamation mechanism, triggering the operating system's OOM killer to terminate the process consuming the most memory. When the memory usage of a DB instance spikes to 100% due to memory leaks, large queries, or batch operations, the OOM killer may terminate the primary or read replica process, triggering a primary/standby switchover (the primary node is killed) or a read replica reboot. As a result, services are interrupted for a short period of time or read requests fail.

Typical symptoms include memory usage spiking from a normal value (such as 60%) to 100% within seconds and then dropping sharply, causing service interruptions or performance fluctuations.

Due to data collection intervals, the system may not capture the exact moment when the memory usage reaches 100%. Therefore, the memory monitoring graph may show a peak that is close to, rather than strictly reaching, 100%.

This section focuses on the two most common causes for OOM exceptions: ultra-large SQL statements and a large number of repeated SQL executions.

Table 1 OOM scenario analysis

Cause

Core Problem Description

Typical Characteristics

Ultra-large SQL statements

A single or a few ultra-large SQL statements suddenly occupy a large amount of memory, causing the memory usage to exceed 100% and triggering an OOM exception.

  • The SQL statements involve large tables (for example, tens of millions of data records).
  • There are multi-table JOIN, ORDER BY, and GROUP BY operations.
  • The IN clause contains too many values (for example, IN (1,2,3,...,10000)).
  • The memory usage of temporary tables or sorting operations increases sharply.

A large number of repeated SQL executions

One or a few types of SQL statements are executed repeatedly in massive volumes. Although the memory usage of an individual SQL statement is low, the huge volume causes memory usage to exceed 100%, triggering an OOM exception.

  • The number of concurrent executions of SQL statements of the same type is extremely high (for example, dozens or hundreds of times).
  • The SQL statements are simple but are frequently called (for example, high-frequency queries or updates).
  • There is no cache or connection pool reuse, leading to repeated parsing and execution.

Cause Analysis

Table 2 Cause analysis for high memory load

Cause

Core Problem Description

Impact

Data volume growth

The amount of table data and indexes increases, requiring the database to cache more data pages and index pages (such as the InnoDB buffer pool).

  • The data volume and index volume keep increasing.
  • Operations such as intermediate result set query, sorting, and hash operations occupy more temporary memory.

Increased number of connections

Each connection consumes a small amount of memory (such as connection buffers, session variables, and temporary tablespaces). In persistent connection scenarios, resource consumption accumulate.

  • The increased number of connections causes memory consumption to accumulate.
  • In persistent connection scenarios, session-level caches or unreleased temporary resources (such as temporary tables and sorting buffers) continuously occupy memory.

Cache policy and hit ratio pursuit

The database proactively expands the cache (such as the buffer pool) to improve the cache hit ratio and reduce disk I/O.

  • The buffer pool automatically expands, occupying more memory.
  • The query plan cache and stored procedure cache expand with increased usage.
  • The cache hit ratio is high, but the memory usage keeps increasing.

Write load change

A large number of write operations cause the memory usage of the log buffer (such as redo log and binlog cache) to increase, and batch operations cause spikes in temporary memory usage.

  • As write volume increases, the memory usage of the log buffer (such as redo log and binlog cache) rises accordingly.
  • Batch operations or large transactions temporarily occupy extra memory (such as sorting and hash operations).

Ultra-large SQL Statements

Ultra-large SQL statements may cause abnormal memory usage increase and OOM exceptions, which are common in small-specification instances. These SQL statements usually involve complex operations, such as large result sets, large table sorting, and multi-table JOIN operations, leading to a sharp increase in temporary memory usage.

Locating method: In slow query logs, audit logs, or full SQL logs, check whether any abnormal SQL statements were executed during the period when the memory usage surged until an OOM exception occurred. If no abnormal SQL statement is found, submit a service ticket.

  1. Check logs:
    • Slow query logs: Unfinished SQL statements may not be recorded, because when an OOM exception occurs, the SQL execution is not yet complete. Only SQL statements that have finished executing are recorded in slow query logs.
    • Audit logs/Full SQL logs: Disabled by default. You need to manually enable the function (Configuring SQL Explorer for a DB Instance or Enabling SQL Audit).
  2. Analyze the logs within the specific time range: Check whether any abnormal SQL statements were executed during the period when the memory usage surged until an OOM exception occurred.

Such SQL statements typically involve the following scenarios:

  • Large result sets: A query returns a large amount of data, causing a sharp increase in memory usage, especially when LIMIT is not used to limit the number of rows returned.
  • ORDER BY on large tables: Sorting large tables without proper indexes consumes a significant amount of memory for the sort buffer.
  • Large table/multi-table JOIN: JOIN operations involving multiple large tables, especially full joins or complex joins, generate large intermediate result sets, consuming a significant amount of memory.
  • Massive IN clauses: Using IN clauses that contain a large number of values can result in complex execution plans and temporary tables, increasing memory overhead.
  • Large-scale inserts and updates: Inserting or updating a large amount of data temporarily consumes a significant amount of memory for transaction logs and buffers.
  • BLOB and TEXT fields: When a query or update operation involves a large number of large fields (such as TEXT and BLOB), the database needs to cache a larger result set in memory, causing a sharp increase in memory usage.

Large Result Sets

  • Symptom

    For SELECT * FROM t1, if the data volume in table t1 is large (for example, millions or tens of millions of rows) and the LIMIT clause is not used to limit the number of rows returned, the result set will occupy a large amount of memory in the server session buffer and client result cache. If the network transmission or client processing speed is slow, the memory cannot be released in a timely manner. As a result, the memory usage may keep increasing, and even an OOM exception may occur.

  • Optimization suggestions
    • Query only necessary fields instead of using select *.
    • Use pagination queries with LIMIT instead of querying all data at a time.

ORDER BY on Large Tables

  • Symptom

    If a query cannot directly return ordered results through indexes (for example, no index is created for the sorting field), the database performs sorting. The memory buffer (sort_buffer) is preferentially used during sorting. The size of the buffer is controlled by the sort_buffer_size parameter.

    • Memory sorting: If the amount of data to be sorted is less than the value of sort_buffer_size, the sorting is completed in memory, which is efficient.
    • Disk sorting: If the amount of data to be sorted exceeds the value of sort_buffer_size, the database writes some data to temporary files on disks, significantly reducing the sorting efficiency.

    Each connection that performs sorting is allocated an independent sort buffer. Even if the sorting memory requirement of a single connection is not high, multiple connections sorting data simultaneously in high-concurrency scenarios may still cause a sharp increase in memory usage and even trigger an OOM exception.

  • Typical query examples
    • Sorting a large table (for example, hundreds of millions of rows) where the sorting field has no index requires a full table scan and sorting.
      SELECT * FROM massive_table ORDER BY non_indexed_column DESC;
    • Complex group aggregation requires sorting before grouping.
      SELECT user_id, COUNT(*) FROM huge_log_table GROUP BY user_id;
    • Sorting the result set after a multi-table JOIN operation where the sorting field has no index requires sorting of massive amounts of data.
      SELECT a.*, b.name FROM big_table a
      JOIN another_table b ON a.id = b.a_id
      ORDER BY a.created_at
      LIMIT 1000 OFFSET 1000000;
  • Optimization suggestions
    • Use indexes to avoid sorting whenever possible. For example, create composite indexes for sorting fields.

      If the query is WHERE a = ? ORDER BY b, create a composite index (a, b) so that ORDER BY can directly use the index sequence to avoid sorting.

    • Decrease the value of sort_buffer_size to ensure that it does not exceed 1 MB. For large-specification instances, you can moderately increase the value, but be aware of the memory accumulation effect in high-concurrency scenarios.

Large Table/Multi-table JOIN

  • Symptom

    When a JOIN operation is performed on a large table and the join condition does not have an index, the database uses the join buffer to cache the scanned rows to accelerate the JOIN operation.

    If the join field (such as non_indexed_key) does not have an index, MySQL needs to perform a full table scan and load all matching rows to the join buffer.

    The larger the data volume, the higher the memory usage of the join buffer, which may quickly exhaust memory resources.

    The join buffer is independently allocated for each JOIN operation. In high-concurrency scenarios, multiple connections perform JOIN operations at the same time, and each connection is allocated an independent join buffer. As a result, the memory usage increases linearly, which may trigger an OOM exception.

  • Typical query examples
    • Scenario 1: No index is available for the join condition. As a result, a full table scan is required, and a large amount of data needs to be loaded to the join buffer.
      SELECT *
      FROM large_table_a A
      JOIN large_table_b B ON A.non_indexed_key = B.non_indexed_key;
    • Scenario 2: A Cartesian product (without the ON condition) causes all rows in the two tables to be combined, generating a massive M x N result set, which consumes a large amount of memory.
      SELECT * FROM table1, table2;
  • Optimization suggestions
    • Ensure that the join columns have indexes.
      • The join columns of the driven table must have indexes (such as t2.t1_id), which is key to optimizing the performance of JOIN operations.
      • Create indexes for columns used in the ON clause.

      SQL example:

      Assume the query: SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id.

      An index needs to be created for t2.t1_id.
      CREATE INDEX idx_t1_id ON t2(t1_id);
    • Select the driving table and driven table.

      MySQL usually selects a small table as the driving table (which can be forcibly specified using STRAIGHT_JOIN) to reduce the number of iterations. A large table is used as the driven table, where matching is accelerated via indexes.

      The following SQL statement forcibly specifies the driving sequence, with small_table as the driving table and large_table as the driven table.

      SELECT* FROM small_table t1 STRAIGHT_JOIN large_table t2 ON t1.id = t2.ref_id;
    • Verify the optimization effect using EXPLAIN.
      • type field: Check whether the driven table uses an index (such as ref or range).
      • rows field: Evaluate whether the number of scanned rows decreases due to index optimization.
      • Extra field: Check whether "Using filesort" or "Using temporary" is displayed (further optimization is required).

Massive IN Clauses

  • Symptom
    • Small-scale IN clauses: When the number of values in the IN list is small (for example, a few dozens), MySQL uses a temporary array to store matching values, resulting in low memory usage.
    • Large-scale IN clauses: When the number of values in the IN list is large (for example, thousands), it may exceed the limit specified by range_optimizer_max_mem_size. As a result, the optimizer abandons range scan optimization and falls back to full table scan, indirectly causing a sharp increase in memory usage. In addition, an ultra-large IN list increases the time required for SQL parsing and optimization. It is recommended that the number of values in the IN list be limited to within 200. If the number exceeds 200, use batch query or temporary tables instead.
  • Typical query examples

    The IN list contains 1,000 IDs.

    SELECT * FROM users WHERE id IN (1, 2, 3, ..., 1000);

    Optimization strategy: batch query + service layer combination

    Split the large IN list into multiple small batches to reduce the memory usage of a single query and prevent the hash table from becoming too large.

    1. Batch query:
      • Split 1,000 IDs into 10 batches, with 100 IDs in each batch.
      • Only 100 IDs are processed in each query, reducing the memory usage of the hash table for a single query.
      -- Batch 1: 100 IDs
      SELECT * FROM users WHERE id IN (1, 2, ..., 100);
      -- Batch 2: 100 IDs
      SELECT * FROM users WHERE id IN (1001, 1002, ..., 200);
      -- Batch 10: 100 IDs
      SELECT * FROM users WHERE id IN (9001, 9002, ..., 1000);
    2. Service layer combination:

      Combine the 10 batches of query results at the application layer (such as Java and Python) to return the complete data.

Large-Scale Inserts and Updates

  • Symptom

    During large-scale concurrent insert or update operations, especially in large transaction scenarios, the InnoDB engine needs to maintain transaction-related lock information and dirty pages in the buffer pool. In addition, memory resources related to the redo log buffer and undo logs are not completely released before the transaction is committed. If a large number of secondary indexes are updated, the buffer pool usage will increase significantly, which may cause a sharp increase in memory usage and even trigger an OOM exception.

  • Causes for memory consumption
    • Transaction resource overhead
      • Undo log: used for transaction rollback and Multi-Version Concurrency Control (MVCC). It occupies memory before a transaction is committed.
      • Redo log: records modifications made by transactions to data pages to ensure crash recovery.
    • Secondary index update:

      Each update to an index field (for example, UPDATE table SET col1 = value1, col2 = value2) requires maintenance of the corresponding index tree, leading to linear growth in memory consumption.

  • Optimization suggestions
    • Limit the number of values inserted at a time to within 100 to prevent a single transaction from becoming too large.
      Commit the transaction (COMMIT) promptly for each batch to release occupied undo/redo resources.
      -- Insert data in batches (100 rows per batch).
      INSERT INTO users (id, name) VALUES  (1, 'Alice'), (2, 'Bob'), ..., (100, 'Zoe');
    • Use LIMIT N to control the number of rows updated at a time (recommended: N ≤ 100) to avoid large transactions.

      Avoid creating unnecessary indexes on columns that are frequently updated to reduce the overhead of updating secondary indexes.

      -- Update data in batches (100 rows per batch).
      UPDATE large_table 
      SET status = 'active' 
      WHERE condition = true 
      LIMIT 100;

BLOB and TEXT Fields

  • Symptom

    When a query or update operation involves a large number of large fields (such as TEXT and BLOB), the database needs to cache a larger result set in memory, causing a sharp increase in memory usage. Specific scenarios include:

    • Result set caching: When a large field is queried (for example, SELECT big_text_col FROM logs), the entire field needs to be loaded into memory.
    • Sorting operation: When a large field is sorted, only the first max_sort_length bytes and rowid are stored in the sort buffer, and the complete field content is not loaded. However, the complete field still needs to be read into memory during the result set transmission phase.
    • Deduplication operation: SELECT DISTINCT big_text_col requires maintaining a temporary hash table or sorting structure in memory.
    • Update operation: Updating large fields in batches (for example, UPDATE logs SET big_text_col = REPEAT('a', 10*1024*1024)) generates a large amount of temporary data, which occupies memory.
  • Typical examples
    • Scenario 1: Query a large field, with the result set occupying a large amount of memory.
      SELECT big_text_col FROM logs;  
    • Scenario 2: Sorting a large field causes a sharp increase in memory usage.
      SELECT * FROM logs ORDER BY big_text_col;  
    • Scenario 3: Deduplicating a large field requires maintaining a temporary structure.
      SELECT DISTINCT big_text_col FROM logs;  
    • Scenario 4: Updating a large field in batches generates temporary data.
      UPDATE logs SET big_text_col = REPEAT('a', 10*1024*1024);
  • Optimization suggestions
    • Limit the amount of data to be queried.
      • Use pagination query: Use LIMIT to control the number of rows returned at a time, preventing excessive data from being loaded at once.
      • Avoid full table scans: If only some fields are needed, avoid using SELECT * and query only the necessary fields.
    • Optimize sorting and deduplication.
      • Reduce the sorting field size: If the sorting field is a large field, extract the key part (such as the prefix) for sorting.
      • Use indexes: Create indexes for sorting fields to avoid memory sorting.
    • Control update operations.
      • Perform batch updates: Split large field updates into small batches to prevent a single transaction from being too large.
      • Avoid redundant updates: Update only the rows that actually need to be modified to reduce unnecessary data processing.

A Large Number of Repeated SQL Executions

  • Scenarios

    After a service change (for example, the rollout of new SQL statements), certain types of SQL statements are frequently executed. Although the memory usage of a single SQL statement is not high, the surge in concurrency or execution count leads to continuous memory usage accumulation, eventually triggering an OOM exception.

  • Locating problematic SQL statements
    1. Time range identification: Analyze slow query logs, audit logs, or full SQL logs to determine the time range from when the memory usage starts to increase to when the OOM exception occurs.
    2. Log analysis:
      • Slow query logs: Check whether there are high-frequency repeated SQL statements (such as SELECT * FROM table WHERE id IN (...)) in slow query logs.
      • Audit logs/Full SQL logs: Use SHOW ENGINE INNODB STATUS or performance_schema to analyze the SQL execution frequency.
    3. Tool-assisted analysis: Use EXPLAIN to analyze the SQL execution plan and check whether full table scans, temporary tables, or sorting operations are involved.
  • Solutions
    • Optimize the SQL logic:
      • Avoid repeated queries (for example, by introducing a caching mechanism).
      • Split complex SQL statements (for example, by using pagination queries or reducing the number of values in the IN clause).
      • Add indexes (for example, on the WHERE and JOIN fields).
    • Enable SQL throttling:

      Use SQL throttling to restrict the concurrency of high-frequency SQL statements.