MySQL如何使用内存
MySQL使用buffer和cache提升数据库操作性能。可以通过提高buffer和cache相关的系统变量来提升MySQL的性能。你也可以通过修改这些值来让MySQL运行在内存有限的系统上。
以下的列表列出了MySQL使用内存的几个方式。并且也列出了相关的系统变量。某些点是存储引擎相关的特色。
-
InnoDB buffer pool 用于缓冲InnoDB表的数据,索引,还有其他相关的buffer。为了高效的支持读操作,buffer pool被分成多个page,每个page都可以保存多行。为了能够高效的管理缓存,使用列表数据结构实现buffer pool,使用LRU逐出很少用的缓存。更多的信息参考
14.4.3.1-InnoDB buffer pool 章节。buffer pool的大小对系统性能影响很大
- 建议把innodb_buffer_pool_size大小配置成系统内存的百分之50到百分之75,在服务器启动时,InnoDB 使用malloc()函数一次性申请完buffer pool大小的内存。使用innodb_buffer_pool_size控制buffer pool大小,更多信息参考配置InnoDB buffer pool size。
- 在大内存系统中,把buffer pool切分成多个buffer pool instances可以提高并发性能。使用innodb_buffer_pool_instances控制buffer pool数目。
- 太小的buffer pool可能会导致随后使用的page从buffer中刷出。
- buffer pool太大可能会导致过多的内存页交换。
-
所有线程共享MyISAM key buffer,使用key_buffer_size变量控制它的大小。
当打开MyISAM数据表时,每个并发运行的线程都会打开一份数据文件,索引文件。对每个并发线程,都有一个表结构,每个列的列结构,3*N的buffer(N是最大的行长度,不包括BLOB列)。BLOB列真实字节数是原BLOB长度加5到8字节。MyISAM存储引擎维护一个内部使用的行buffer
-
myisam_use_mmap 系统变量设置成 1 会打开MyISAM表的内存映射。
-
如果内部内存表太大(根据tmp_table_size和max_heap_table_size),MySQL自动把数据表转换成磁盘临时表,基于MyISAM存储引擎。你可以增大最大临时表大小,更多参考8.4.4-MySQL内部临时表
对于使用create table语句建立的MEMORY存储引擎的表,使用变量max_heap_table_size控制它的大小,超过这个限制后,内存表也不会转换成磁盘临时表。
-
MySQL Performance Schema是低级别监控MySQL的功能。出于性能的考虑,在服务启动时给Performance Schema设置一个固定的内存,也不要在运行时修改它的大小。
-
每个管理客户端连接的线程还需要一些线程内部的空间。下面列出这些信息和相关的控制参数。
- 线程栈(使用thread_stack变量控制)
- 一个连接用的buffer(使用net_buffer_length变量控制)
- 一个结果用的buffer(使用net_buffer_length变量控制)
每个连接用的buffer,结果用的buffer初始值是net_buffer_length 字节,最大动态增长到max_allowed_packet 字节数。在每个SQL statement 执行后, 结果用的buffer会收缩到net_buffer_length 字节数目。当statement 运行时,还需要开辟空间保存当前statement的字符串副本。
每个连接线程使用内存计算statement 摘要。 参考22.10-Performacen Schema Statement Digests。在 MySQL 5.6.24 每个会话最多使用max_digest_length字节计算摘要。 -
所有线程共享相同的基本内存。
-
当某个线程不再需要时,如果线程不是返回线程缓存,线程的内存会返还给系统。这样的话,内存需要重新申请。
-
执行顺序扫表的请求都会申请一个read buffer(read_buffer_size变量控制)
-
任意顺序读表时(比如,排序),使用random-read buffer避免磁盘寻道(使用变量read_rnd_buffer_size控制buffer大小)。
-
所有的连接操作都在一个过程中执行,大多数连接操作都可以在不需要临时表的情况下完成。大多数临时表是基于内存的hash表,如果行太长(所有列长度的总和)或者包含BLOB列,使用磁盘临时表。
-
大多数请求需要执行sort操作,执行sort操作时,根据结果集大小需要开辟一个sort buffer和0到多个临时文件,更多请参考4.3.5-MySQL在哪里存储数据
-
几乎所有解析和计算操作都在线程内,并且复用内存池。此时对于小item不需要重新申请空间,也能省去开辟空间,释放空间的过程。仅在大String时才需要申请空间。
-
对于有BLOB的列,使用一个动态扩张的buffer来读取大的BLOB值。如果扫描一个表,buffer的大小和最大BLOB值相等。
-
MySQL正在使用的表缓存(table cache)需要内存和文件描述符。使用FIFO数据结构保存这个数据。初始化表缓存大小使用系统变量table_open_cache控制。更多请参考8.4.3.1-MySQL怎么打开关闭数据表
MySQL同样需要内存保存表定义缓存。table_definition_cache系统变量定义了最大可保存的表定义(来自.frm文件)缓存。如果你使用的表数目多,你可以调整这个变量的值相对大一点来加速表打开的速度。和表缓存不同,表定义缓存不需要文件描述符,使用的内存也更少。
-
FLUSH TABLES语句和mysqladmin flush-tables命令关闭所有未使用的表,并且在并发线程执行完毕后,标记所有的表已经关闭,这个操作有效的释放了大部分在使用的内存。FLUSH TABLES语句只在所有的表都已经关闭后才会返回。
-
执行GRANT,CREATE USER,CREATE SERVER,INSTALL PLUGIN语句产生服务器层缓存。执行REVOKE,DROP USER,DROP SERVER,UNINSTALL PLUGIN也不会释放这些缓存,多次执行这些语句会这个缓存内存使用变高。执行FLUSH PRIVILEGES语句可以清除这个缓存。
ps和其他某些系统命令可能会报告mysqld占用了大量内存。这个可能是因为不同内存地址的线程栈导致的。比如,Solaris系统的ps命令把栈之间未使用的空间当做使用的空间统计。如果需要确认这个,使用swap -s。我们使用过多个内存测试工具测试mysqld,所以应该不会出现memory leaks。
开启大内存页(large page)的支持
某些硬件和操作系统体系结构支持的内存页大小比默认页(通常是4KB)还大。实际的真实情况由底层的硬件和操作系统。应用程序通常会执行许多内存访问,提高内存页的大小来减少TLB(翻译后备缓冲器)缓存缺失,通常能提高性能。
在MySQL中,InnoDB能使用大的内存页来开辟缓冲池和其他内存池所需的空间。
MySQL中使用的标准页最大可到4MB。在最近发布的Solaris的super large pages功能最大可以到256MB。使用--super-large-pages打开此功能,关闭使用--skip-super-large-pages选项。
MySQL同样也支持Linux实现的large page(Linux中的Huge TLB)。
在使用Linux的large page功能之前,必须先开启内核对此功能的支持,也需要配置Huge TLB的内存池。更多请参考Huge TLB的文档,它通常在Linux 资源的/Documentation/vm/hugetlbpage.txt中。
最近Red Hat发布的Linux企业版中,默认开启此功能。可以使用如下命令确认。
shell> cat /proc/meminfo | grep -i huge
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 4096 kB
输出非空表示支持large page功能,值是0表示并未配置这个功能。
如果你需要配置large page功能,查看hugetlbpage.txt。
假设你的Linux内核支持large page,使用以下命令配置MySQL使用large page。通常,你需要把它放在rc文件中,或者放在有相同功能的启动文件中,这样在系统每次重启后都能执行它。这些命令需要在MySQL 服务启动之前启动。根据你的系统,配置合适的大小。
# Set the number of pages to be used.
# Each page is normally 2MB, so a value of 20 = 40MB.
# This command actually allocates memory, so this much
# memory must be available.
echo 20 > /proc/sys/vm/nr_hugepages
# Set the group number that is permitted to access this
# memory (102 in this case). The mysql user must be a
# member of this group.
echo 102 > /proc/sys/vm/hugetlb_shm_group
# Increase the amount of shmem permitted per segment
# (12G in this case).
echo 1560281088 > /proc/sys/kernel/shmmax
# Increase total amount of shared memory. The value
# is the number of pages. At 4KB/page, 4194304 = 16GB.
echo 4194304 > /proc/sys/kernel/shmall
对于MySQL的场景,正常来说通常把shmmax设置成接近shmall的值。
可以使用如下命令检查你的配置。
shell> cat /proc/meminfo | grep -i huge
HugePages_Total: 20
HugePages_Free: 20
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 4096 kB
使用hugetLb_shm_group的最后一步是给mysql用户一个"unlimited"的内存边界值。编辑/etc/security/limits.conf或者在mysqld_safe脚本内添加如下命令。
ulimit -l unlimited
在切换mysql用户之前。mysqld_safe中添加的ulimit命令会导致root用户设置的内存边界是unlimited(假设启动mysqld_safe的用户是root)。
MySQL默认关闭对large page的支持。启动参数添加--large-pages开启这个功能。比如,你可以在my.cnf中添加如下参数。
[mysqld]
large-pages
添加这个选项后,InnoDB的缓冲池和额外的内存池自动使用large page。如果InnoDB不能使用这个功能,回退到正常的page,然后打印一条错误日志:
Warning: Using conventional memory pool
确认正在使用的large page信息,请再次检查/proc/meminfo:
shell> cat /proc/meminfo | grep -i huge
HugePages_Total: 20
HugePages_Free: 20
HugePages_Rsvd: 2
HugePages_Surp: 0
Hugepagesize: 4096 kB