There are many blog posts already written on topics related to “MySQL server memory usage,” but nevertheless there are some who still get confused when troubleshooting issues associated with memory usage for MySQL. As a Percona support engineer, I’m seeing many issues regularly related to heavy server loads – OR OOM killer got invoked and killed MySQL server due to high Memory usage… OR with a question like: “I don’t know why mysql is taking so much memory. How do I find where exactly memory is allocated? please help!”
Checking MySQL Server Memory
There are many ways to check the memory consumption of MySQL. So, I’m just trying here to explain it by combining all the details that I know of in this post.
- Check memory related Global/Session variables.
If you are using MyISAM then you need to check for Key_buffer_size, while using InnoDB, you can check innodb_buffer_pool_size, innodb_additional_memory_pool_size, innodb_log_buffer_size, innodb_sort_buffer_size (used only for sorting data while creating index in innodb, introduced from 5.6). max_connections, query_cache_size and table_cache are also important variables to check.
We know that whenever a thread is connected to MySQL, it will need it’s own buffers when they are doing some complex operations like FTS, sorting, creating temp tables, etc. So we also need to check the size of read_buffer_size, sort_buffer_size, read_rnd_buffer_size and tmp_table_size.
There is a very good quote from So it seems we have to understand the purpose for configuring any variable… either it is Global or Session level. I would like to explain more about that here.
For the Global variables like key_buffer_size, query_cache_size etc, MySQL always allocates and initializes the specified amount of memory all at once when the server starts. But it’s not happened for those who are global default but can be set as per-session variables, i.e For read_buffer_size, sort_buffer_size, join_buffer_size, MySQL doesn’t allocate any memory for these buffers until query needs. But when a query needs, it immediately allocates the entire chunk of memory specified. So if there are even small sorts, the full buffer size will be allocated which is just a waste of memory. Even some buffers can be used multiple times. For example, on queries that join several tables, join_buffer can be allocated once per joined table. Also, some complicated queries including sub-queries can use multiple sort_buffers at the same time which can lead to high memory consumption. In some scenario, the query didn’t even use sort_buffer whatever size is, as it selects by the primary key which will not allocate it. So it depends on the nature of your environment but I would say it’s always better to start with a safe variable value that can be larger than default if needed but not as large as it can consume all of the server’s memory.
One more thing, not all per-thread memory allocation is configured by variables. Some of the memory allocation per thread is done by MySQL itself for running complex processes/queries like “stored procedures” and it can take an unlimited amount of memory while running. And sometimes, optimizer can also take a lot of memory working with highly complex queries which generally we can’t control by any configuration parameter.
Even innodb_buffer_pool_size is not a hard limit, usually, innodb uses 10% more memory than the one specified. Many people do not recommend using both storage engine MyISAM and InnoDB at the same time on the production server. Because both have individual buffers which can eat all server memory.
For detailed information related to this topic, I would suggest reading this post from Peter Zaitsev titled “MySQL Server Memory Usage.”
- Check “SHOW ENGINE INNODB STATUS” for section “BUFFER POOL AND MEMORY“
1 2 3 4 5 6 7 8 9 10 |
---------------------- BUFFER POOL AND MEMORY ---------------------- Total memory allocated 137363456; in additional pool allocated 0 Dictionary memory allocated 76056 Buffer pool size 8191 Free buffers 7804 Database pages 387 Old database pages 0 Modified db pages 0 |
Above one is from Native MySQL but if you’ll check the same with Percona Server you’ll get some more information.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
---------------------- BUFFER POOL AND MEMORY ---------------------- Total memory allocated 137756672; in additional pool allocated 0 Total memory allocated by read views 88 Internal hash tables (constant factor + variable factor) Adaptive hash index 2217584 (2213368 + 4216) Page hash 139112 (buffer pool 0 only) Dictionary cache 597885 (554768 + 43117) File system 83536 (82672 + 864) Lock system 333248 (332872 + 376) Recovery system 0 (0 + 0) Dictionary memory allocated 43117 Buffer pool size 8191 Buffer pool size, bytes 134201344 Free buffers 7760 Database pages 431 Old database pages 0 Modified db pages 0 |
This will give you information regarding how much memory is allocated by InnoDB. You can see here “Total Memory Allocated”, “Internal Hash Tables”, “Dictionary Memory Allocated”, “Buffer Pool Size” etc.
- Profiling MySQL Memory usage with Valgrind Massif
Recently, I used this tool and surprisingly I got very good statistics about memory usage. Here the only problem is you have to shut down the mysql, start it with valgrind massif and after collecting statistics, you again have to shut down and normal start.
1 2 3 |
$ /etc/init.d/mysql stop $ valgrind --tool=massif --massif-out-file=/tmp/massif.out /usr/sbin/mysqld $ /etc/init.d/mysql restart |
After getting the massif.out file, you have to read it with ms_print command. You will see a pretty nice graph and then statistics. i.e
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
[root@percona1 ~]# ms_print /tmp/massif.out -------------------------------------------------------------------------------- Command: /usr/sbin/mysqld Massif arguments: --massif-out-file=/tmp/massif.out ms_print arguments: /tmp/massif.out -------------------------------------------------------------------------------- MB 50.22^ ## | # | # | # | # | # | : # | :: # ::::::@:::::::::::::@:::@::::@:::@:::: | : @::# :::::@@::::::::::::::::::: @::: ::: :::::@:::@::: @:::@:::: | : @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@:::: | : @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@:::: | : @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@:::: | : @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@ | : @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@ | :: @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@ | ::: @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@ | :::: @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@: | :::: @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@: | :::: @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@: | :::: @::# : ::@ :::::::::: :: ::::: @::: ::: :::::@:::@::: @:::@::::@: 0 +----------------------------------------------------------------------->Mi 0 575.9 Number of snapshots: 96 Detailed snapshots: [1, 7, 11 (peak), 16, 35, 48, 58, 68, 78, 88] -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 0 0 0 0 0 0 1 6,090,089 195,648 194,590 1,058 0 99.46% (194,590B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->48.79% (95,458B) 0x7A1D20: my_malloc (my_malloc.c:38) | ->25.08% (49,060B) 0x6594F1: read_texts(char const*, char const*, char const***, unsigned int) (derror.cc:160) | | ->25.08% (49,060B) 0x6597C2: init_errmessage() (derror.cc:69) | | ->25.08% (49,060B) 0x506232: |