§ 034 · MySQL

MySQL 8.4 Memory Limits: tmp_table_size vs. temptable_max_ram

I recently got into a bit of a debate about standardizing MySQL 8.4’s internal temporary table configuration. We wanted to cap memory usage efficiently, but relying on “rules of thumb” isn’t enough when production stability is at stake.

The main confusion was about how per-query limits fight with global limits. So, rather than guessing, I decided to break a sandbox environment to see exactly where the bytes go—and confirm the findings against the official documentation.

The Theory: Individual vs. Collective

Before I run the scripts, let’s establish the rules based on the MySQL 8.4 Reference Manual. There is a critical distinction between “individual” and “collective” limits.

1. tmp_table_size (The Cup)

This is the limit for a single table. According to the documentation:

“Defines the maximum size of any individual in-memory internal temporary table created using the TempTable storage engine.”

If a single query tries to exceed this:

“When the limit determined by tmp_table_size is reached, MySQL automatically converts the in-memory internal temporary table to an InnoDB on-disk internal temporary table.”

This confirms the first premise: this variable protects us from individual bad queries.

2. temptable_max_ram (The Bucket)

This is the global limit. The documentation states:

“Defines the maximum amount of RAM that can be used by the TempTable storage engine before it starts allocating space from memory-mapped files or before MySQL starts using InnoDB on-disk internal temporary tables…”

It also clarifies the relationship between the two:

“The tmp_table_size limit is intended to prevent individual queries from consuming an inordinate amount of global TempTable resources…”

The “Cup vs. Bucket” Interaction: The manual explicitly defines what happens when these two interact:

“If tmp_table_size is less than temptable_max_ram, it is not possible for an in-memory temporary table to use more than tmp_table_size.”

The Theory: If we set the global bucket (temptable_max_ram) to 2GB and the individual cup (tmp_table_size) to 256MB, queries shouldn’t go beyond that 2GB of RAM when using temp space collectively.

The Reality: If you have 10 queries running, and each needs ~200MB, they fit in RAM (Total: 2GB). But if an 11th query shows up, the global temptable_max_ram limit is hit.

Once that global limit is hit, the behavior depends on your configuration.

The Setup

To test this without needing terabytes of RAM, I set the limits ridiculously low to force the behavior immediately.

The Config:

The Query: I used a CTE to generate some data (just enough to overflow the 16MB individual limit) and forced a temp table using ORDER BY RAND().

I ran this in parallel 50 times:

The Results

Checking the Performance Schema confirmed the behavior.

Output:

The Created_tmp_disk_tables count went up. This aligns with the docs:

“When an internal temporary table is created on disk, the server increments the Created_tmp_disk_tables value.”

The “Invisible File” Mystery

Next, I wanted to test temptable_use_mmap = ON.

According to the documentation, this setting changes the overflow mechanism:

“Controls whether the TempTable storage engine allocates space from memory-mapped files or MySQL uses InnoDB on-disk internal temporary tables when the limit determined by temptable_max_ram is exceeded.”

I enabled it and ran the test again. But here is the catch: You can’t see the files. The documentation explains exactly why ls -la /tmp shows nothing:

“Temporary files are deleted immediately after they are created and opened, and therefore do not remain visible in the tmpdir directory. The space occupied by temporary files is held by the operating system while temporary files are open.”

Furthermore, standard metrics fail us here:

“Due to a known limitation, Created_tmp_disk_tables does not count on-disk temporary tables created in memory-mapped files.”

To prove this was actually working, I had to use the Performance Schema instruments recommended by the manual (memory/temptable/physical_disk) and write a Linux-level script to monitor /proc file descriptors.

The Monitor Script:

The Logs:

This output confirms the hybrid behavior we expected:

  1. Standard Disk I/O: Queries exceeding tmp_table_size (16MB) were booted to InnoDB.

  2. Memory Mapped: Queries that fit individually but hit the Global limit (32MB) spilled to mmap files.

The Verdict

We confirmed that temptable_use_mmap does exactly what it says—it changes the overflow mechanism for the global limit.

However, you probably shouldn’t use it. The documentation is blunt:

“temptable_use_mmap is deprecated; expect support for it to be removed in a future version of MySQL.”

Since it’s deprecated, standardization is a bad idea.

The recommendation:

  1. Keep temptable_max_ram high (1GB+): The docs define the default as “3% of the total memory available on the server, with a minimum of 1 GB”. Don’t starve the global pool.

  2. Tune tmp_table_size individually: If you need to control memory, do it here. Let the bad queries spill to disk (InnoDB) individually without punishing the rest of the server.

Wrapping Up

The biggest insight here isn’t just about memory settings—it’s about understanding the scope of your tools. We proved that temptable_max_ram acts as a safety valve for the entire server, while tmp_table_size is your per-query disciplinarian. The recommendation is to stick to the defaults for the engine, keep your global limits healthy (1GB+), and focus on tuning your bad queries.

See you next time!

Written by

Vinicius Grippa

Writes this blog. Mostly about databases. Boring on purpose.

More about me →

The floor is yours.

0 comments · Moderated · civil & on-topic

First comment appears here once approved. Questions, corrections, and counterpoints welcome — just no self-promotion.

Add a comment

Your email address is never published. * required

Subscribe · Posted when ready

A quiet, technical email about databases.

One post per send, corrections when I’m wrong, nothing else. No social-media cross-posts. No “what we learned.”

Unsubscribe with any reply