检查内存占用
简介
performance_schema是对mysql的细力度的性能监控诊断工具,采集到的性能数据使用performance引擎存储,全部保存在内存。 本文关注 pfs 的对mysql内存使用相关的统计。首先从代码简单解释pfs内存统计的简单实现以及介绍下 pfs 统计内存相关的参数,然后介绍相关的表的使用介绍和,最后介绍一个监控项为例。
PFS内存统计的简单原理
在mysql系统中,为了统计内存的使用,提供了一套可以统计内存的使用内存管理接口,详见pfs_memory_privider.h 。大概实现思路为,定义key-value模型,相关模块的内存定义一个key,根据开辟和释放的虚拟内存,更改key的值,这个key名到具体实现中为event_name。但这个key 有不同的维度,可以是thread,accout,user,host,global。内存在开辟的时候,根据key的属性是否为global,保存thread或者global上。但保存的value会根据线程的退出等操作在不同维度间转移。
参数配置
consumers
永久修改
在配置文件中修改:
//控制global属性的instrument
performance_schema_consumer_global_instrumentation=ON
//控制thread属性的instrument
performance_schema_consumer_thread_instrumentation=ON
临时改动
更新performance_schema.setup_consumers的enable列值
mysql> select * from setup_consumers where name like '%instrumentation';
+------------------------+---------+
| NAME | ENABLED |
+------------------------+---------+
| global_instrumentation | YES |
| thread_instrumentation | YES |
+------------------------+---------+
2 rows in set (0.00 sec)
###关闭thread上的instrumentation收集
update setup_consumers set enabled = 'NO' where name = 'thread_instrumentation';
instruments
永久修改
在配置文件中修改
performance_schema_instrument='memory%=ON'
临时改动
更新performance_schema.setup_instruments的enable列值
select * from setup_instruments where name like '%memory%io_cache%' limit 1\G
*************************** 1. row ***************************
NAME: memory/sql/TABLE::sort_io_cache
ENABLED: YES
TIMED: NULL
PROPERTIES:
VOLATILITY: 0
DOCUMENTATION: NULL
select * from setup_instruments where name like 'memory%' limit 1\G
*************************** 1. row ***************************
NAME: memory/performance_schema/mutex_instances
ENABLED: YES
TIMED: NULL
PROPERTIES: global_statistics
VOLATILITY: 1
DOCUMENTATION: Memory used for table performance_schema.mutex_instances
##更新语法
update setup_instruments set ENABLED = 'NO' where name like 'memory/mysys/IO_CACHE';
memory模块的 intruments从他们记录的内存内容可以分为两类:
PFS内置内存
performance_schema模块本身使用内存,名为memory/performance_schema/%,不调用注册接口直接注册的。
其它模块。
其它模块使用的内存,使用pfs_register_memory_vc注册的。
这里对memory模块用到intruments做一个解释说明,整体的请看https://dev.mysql.com/doc/refman/8.0/en/performance-schema-setup-instruments-table.html
NAME:
是注册的key名,在其它地方体现为event_name.
ENABLED
开关,PFS管理模块的ENABLED不能变化,代码中有限制。
TIMED
在memory模块没有tiemed属性,这类的还有error,thread.
PROPERTIES
在memory模块只有global_statistics 或无属性,global_statistics直接保存在global维度上,无属性开始保存在thread维度上,后面可能迁移到其它维度上。
VOLATILITY
提示信息,可以理解为提示生命周期。在peformance_schema类别下的intrumentes都是1,表示永恒。其它memory相关的为0,这个是代码中的提示行为。
DOCUMENTATION
注释信息
限制
- performance_schema_max_memory_classes
PFS最多能管理的内存实例数
show variables like 'performance_schema_max_memory_classes';
+---------------------------------------+-------+
| Variable_name | Value |
+---------------------------------------+-------+
| performance_schema_max_memory_classes | 530 |
+---------------------------------------+-------+
- Performance_schema_memory_classes_lost
记录丢失管理的内存实例数
show global status like 'Performance_schema_memory_classes_lost';
+----------------------------------------+-------+
| Variable_name | Value |
+----------------------------------------+-------+
| Performance_schema_memory_classes_lost | 0 |
+----------------------------------------+-------+
相关表信息
performance_schema
在performance_schema中有如下5个表:
memory_summary_by_thread_by_event_name
memory_summary_by_account_by_event_name
memory_summary_by_user_by_event_name
memory_summary_by_host_by_event_name
memory_summary_global_by_event_name
在PFS使用章节中,提到的key-value模型其中key可以在多个维度,表名中的thread,accout,user,host,global就是这些维度。这些表都是统计的意思,既是多个维度上的联合统计,也是可能是单个维度上的统计。
表详细信息
相同字段
EVENT_NAME:对应内存模块名。
COUNT_ALLOC:合计开辟内存次数。
COUNT_FREE:合计释放内存次数。
SUM_NUMBER_OF_BYTES_ALLOC:合计开辟内存大小。
SUM_NUMBER_OF_BYTES_FREE:合计释放内存大小。
LOW_COUNT_USED:历史最少使用的数量,都是0。
CURRENT_COUNT_USED:当前在使用的内存数量。
HIGH_COUNT_USED:历史最高在使用的内存数量。
LOW_NUMBER_OF_BYTES_USED:历史最少使用的内存大小。
CURRENT_NUMBER_OF_BYTES_USED:当前使用在使用的内存大小。
HIGH_NUMBER_OF_BYTES_USED:历史最高在使用的内存大小。
其中各个表多出的字段就是维度上的体现。
详细介绍
memory_summary_by_thread_by_event_name
- 统计各个thread上各个event_name占用的内存。根据thread维度上保存数据所得。
- 在thread退出后,对应的key会汇总到account上和部分在global上保存,如果account不存在会汇总到对应的user,host上和部分global上保存,如果acccount,user,host都没有,直接汇总到global上。
- 在thread1上开辟的内存,给thread2 使用并被thread2释放,在thread2释放时发现这块内存不属于自己管理,会将这部分释放的内存记录在对应的global维度上。
memory_summary_by_account_by_event_name
- 统计各个account上各个event_name占用的内存。根据thread纬度上和account维度上保存的数据聚合求的。
- 在truncate performance_shcema.users时,将account上的数据转移到host 和 user上保存,如果host 和 user 不存在,直接转移到global上保存,并将user维度上保存的数据清空。
memory_summary_by_user_by_event_name
1.统计各个user,既username相同的,各个event_name占用的内存。根据thread, account, user维度上的数据聚合求的。
2.不能再转移了,在truncate performance_shcema.users 时清空。
memory_summary_by_host_by_event_name
- 统计各个host,既hostname相同的,各个event_name占用的内存。根据thread, account, host纬度上的数据聚合求的。
- 在truncate performance_shcema.hosts时,先将account上的内容转移,再将host上的内容,转移到global上保存。
memory_summary_global_by_event_name
1.统计各个key,既event_name总共占用的内存,根据thread, account, host, global维度上的数据聚合求的。
SYS
在sys中库中,将performance_schema中的memory表,根据维度做group by,将对应的event_name占用的内存值求sum,创建了几个统计内存的view,体现维度上总共消耗的内存。简单理解为performance_schema的显示(维度,event_name,内存信息),sys(维度,内存信息),但global相关的两者类似,详情请具体的sys表的实现,涉及到的表如下:
## 通过format_bytes转化最终结果,为带单位的字符串,更直观 memory_by_host_by_current_bytes memory_by_thread_by_current_bytes memory_by_user_by_current_bytes memory_global_by_current_bytes memory_global_total ## 展示的原始数值 x$memory_by_host_by_current_bytes x$memory_by_thread_by_current_bytes x$memory_by_user_by_current_bytes x$memory_global_by_current_bytes x$memory_global_totalmemory_global_total
统计当前pfs系统中总共统计到的虚拟内存。
memoryby\*_by_current_bytes
该类表统计对应维度下内存的总体使用情况。都包含了如下列:
current_count_used:当前在使用的内存数量
current_allocated:当前分配的内存大小
current_avg_alloc:当前平均每次分配的内存大小
current_max_alloc:当前最大实例占用的内存
total_allocated :历史总共开辟的内存大小。
memory_global_by_current_bytes
统计各个实例使用的内存情况。
current_count:当前在使用的内存数量
current_alloc:当前分配的内存大小
current_avg_alloc:当前平均每次分配的内存大小
high_count:历史最高使用的内存数量
high_alloc:历史最高分配的内存大小
high_avg_alloc:历史最高平均每次分配的内存大小,这个值不准确。
使用流程
流程
- 结合系统内存工具TOP,/proc/$pid信息等
- 查看PFS统计到的总的内存,sys.memory_global_total
- 细化到线程、实例,结合 sys.memory_by_thread_by_current_bytes, sys.memory_global_by_current_bytes, performance_schema.memory_summary_by_thread_by_event_name,performance_schema.memory_summary_global_by_event_name, 以及实例的详细信息performance_scehma.setup_instruments整体分析。
小结
通过TOP或者/proc/pid/status等,看到mysql占用的实际内存,在很多情况下都比pfs总共统计(sys.memory_global_total)的内存多很多,这是一个正常现象。
- 在代码中,有很多使用内存的并没有使用pfs的内存统计。
- 进程本身占用的内存。可以分析/proc/$pid/status ,/proc/$pid/smaps这些。
pfs统计是虚拟内存。
案例分析
下面我们以一个global_statistics 和 一个非global_statistics的实例做一个演示。
memory/sql/Logevent
四川移动环境的发现sql/slave_sql的current_allocated180.77GIB,疑似内存泄露的样子
select * from setup_instruments where name like 'memory%Log_event';
+----------------------+---------+-------+------------+------------+---------------+
| NAME | ENABLED | TIMED | PROPERTIES | VOLATILITY | DOCUMENTATION |
+----------------------+---------+-------+------------+------------+---------------+
| memory/sql/Log_event | YES | NULL | | 0 | NULL |
+----------------------+---------+-------+------------+------------+---------------+
- 这里先查看了sys.memory_by_thread_by_current_bytes表,找到一个线程占用的内存比较高
- 通过查看performance_schema.memory_summary_by_thread_by_event_name,确定线程那个event_name占用的比较高,确定了memory/sql/Logevent 这个实例用内存比较多10多区,难道是这块内存泄露了?
- memory/sql/Logevent能在thread上统计到数据可以推出这个实例是非global_statistics的,通过查询setup_intruments表确定该信息。
- 继续查询performance_schema.memory_summary_global_by_event_name表,确定memory/sql/Logevent的内存是正常的。
- 最后总结得出,sql/slave_sql线程是开辟的内存再sql/worker线程上释放了,因为memory/sql/Logevent 是thread上的,释放线程和开辟线程不是同一个线程,就直接记录到global维度上了,在performance_schema.memory_summary_global_by_event_name显示的是才是正确的值。
memory/sql/IO_CACHE
四川移动的测试环境发现下面类似的问题,memory_global_total高,找到了是memory/sql/IO_CACHE 实例占用内存高,但找不到那个线程占用高,情况如下:
GreatDB Cluster[performance_schema]> select * from setup_instruments where name like 'memory/mysys/IO_CACHE';
+-----------------------+---------+-------+-------------------+------------+---------------+
| NAME | ENABLED | TIMED | PROPERTIES | VOLATILITY | DOCUMENTATION |
+-----------------------+---------+-------+-------------------+------------+---------------+
| memory/mysys/IO_CACHE | YES | NULL | global_statistics | 0 | NULL |
+-----------------------+---------+-------+-------------------+------------+---------------+
因为memory/mysys/IO_CACHE是global_statistics的,只保存在global维度上,在线程维度上是查不到的,所以是正常现象。但这里的memory/mysys/IO_CACHE高还是没有查到。(我以为内存泄露了。。。) 但得出三个现象 1.这个内存主要是在IO_CACHE模块 。
2.sys.memory_global_by_current_bytes 中的平均值很高,最少800M以上,那么开辟这个内存存在基数很大的值。
3.进程使用的实际内存并没有涨,而且增长是没有规律的,那么说明开辟了并没有写大量的数据。
思路一:
发现这个问题和定位这个问题时间间隔了一晚,环境在这间隔的时间IO_CACHE正好有增长,是不是有什么大操作导致呢?后面就看slow_log,但没有发现异常语句,这个路就结束了。
思路二:
定位这个问题时,我对pfs内存管理模块理解错误,以为是内存泄露,就是使用通用的内存泄露定位方法,记录了进程的pmap信息,想通过dump增长段内容的方式分析,但这是个远程环境,一点点分析内存太难操作了,就搁浅了。在这期间还分析了进程打开的临时文件这些,IO_CACHE可能会涉及到文件,但也没有异常。
思路三:
结合variables 信息,在现象2中,可以知道这个IO_CACHE开辟的内存很大,结合variables中%size大于800M,且可能使用到IO_CACHE的变量去找对应使用IO_CACHE代码,最后找到了是线程在一次记录binlog模块时开辟的IO_CACHE导致,对应的变量为binlog_cache_size,并在线程退出才释放。
show variables like 'binlog_cache_size';
+-------------------+-------------+
| Variable_name | Value |
+-------------------+-------------+
| binlog_cache_size | 10737418240 |
+-------------------+-------------+
并在本地复现了这个问题。
小结
- 在使用mysql监控指标时,一定要确定这些指标的意义,避免误判。
- 对mysql的空间相关的配置,一定要仔细研究,不能随意配,如果在案例二,有大量的大事务需要记录binlog,可能会把内存吃满,导致OOM。
扩展
在percona版本中,如果使用了jemalloc,可以通过如下两个表查看jemalloc管理的内存状态。
performance_schema.malloc_stats
select * from malloc_stats;
+-------+-----------+---------+---------+-----------+
| TYPE | ALLOCATED | NMALLOC | NDALLOC | NREQUESTS |
+-------+-----------+---------+---------+-----------+
| small | 145390048 | 588903 | 0 | 594144 |
| large | 199573504 | 5227 | 0 | 8571 |
| huge | 0 | 0 | 0 | 0 |
+-------+-----------+---------+---------+-----------+
performance_schema.malloc_stats_totals
select * from malloc_stats_totals;
+-----------+-----------+-----------+----------+----------+----------+
| ALLOCATED | ACTIVE | MAPPED | RESIDENT | RETAINED | METADATA |
+-----------+-----------+-----------+----------+----------+----------+
| 672081504 | 678526976 | 742391808 | 0 | 0 | 0 |
+-----------+-----------+-----------+----------+----------+----------+
相关解释请看jemalloc对应版本的文档。
附录
sys.memory_global_total表
显示总内存
GreatDB Cluster> select * from sys.memory_global_total;
+-----------------+
| total_allocated |
+-----------------+
| 522.28 MiB |
+-----------------+
1 row in set (0.01 sec)
sys.memory_by_thread_by_current_bytes表
该表用于显示每个线程占用内存的情况,可以显示占用内存最大的N条记录,如下:
GreatDB Cluster> select thread_id tid, user, current_allocated ca, total_allocated from sys.memory_by_thread_by_current_bytes order by total_allocated desc limit 10;
+-----+-----------------------------------------+------------+-----------------+
| tid | user | ca | total_allocated |
+-----+-----------------------------------------+------------+-----------------+
| 59 | group_rpl/THD_certifier_broadcast | 1.28 KiB | 8.40 MiB |
| 33 | innodb/buf_dump_thread | 334 bytes | 63.44 KiB |
| 1 | sql/main | 1.11 MiB | 6.23 MiB |
| 54 | greatdb/sqlnode-manager | 29.38 KiB | 51.50 KiB |
| 36 | innodb/srv_worker_thread | 1.36 KiB | 4.06 MiB |
| 38 | innodb/srv_worker_thread | 1.28 KiB | 396.96 KiB |
| 43 | sql/event_scheduler | 16.38 KiB | 31.46 KiB |
| 50 | greatdb connection pool/connection pool | 3.45 MiB | 3.48 MiB |
| 24 | innodb/srv_master_thread | 1.28 KiB | 3.00 MiB |
| 53 | greatdb/task-mgr | 324.55 KiB | 288.93 MiB |
+-----+-----------------------------------------+------------+-----------------+
10 rows in set (0.40 sec)
performance_schema.memory_summary_by_account_by_event_name表
根据account和event_name显示内存的使用情况
GreatDB Cluster> select USER, HOST, EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_by_account_by_event_name limit 5;
+------+------+-----------------------------------------------------+-------------+---------------------------+
| USER | HOST | EVENT_NAME | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+------+------+-----------------------------------------------------+-------------+---------------------------+
| NULL | NULL | memory/sql/Locked_tables_list::m_locked_tables_root | 0 | 0 |
| NULL | NULL | memory/sql/display_table_locks | 0 | 0 |
| NULL | NULL | memory/sql/THD::transactions::mem_root | 0 | 0 |
| NULL | NULL | memory/sql/Delegate::memroot | 0 | 0 |
| NULL | NULL | memory/sql/THD::main_mem_root | 57 | 668616 |
+------+------+-----------------------------------------------------+-------------+---------------------------+
5 rows in set (0.00 sec)
performance_schema.memory_summary_by_host_by_event_name表
根据host和event_name显示内存的使用情况
GreatDB Cluster> select HOST, EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_by_host_by_event_name order by COUNT_ALLOC desc limit 5;
+-----------+-------------------------------------+-------------+---------------------------+
| HOST | EVENT_NAME | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+-----------+-------------------------------------+-------------+---------------------------+
| NULL | memory/innodb/sync0rw | 213072 | 18750336 |
| NULL | memory/sql/Sid_map::Node | 189438 | 16668688 |
| localhost | memory/innodb/sync0rw | 153793 | 13533784 |
| localhost | memory/sql/Gtid_set::Interval_chunk | 135606 | 25907256 |
| localhost | memory/sql/Sid_map::Node | 113704 | 5606276 |
+-----------+-------------------------------------+-------------+---------------------------+
5 rows in set (0.01 sec)
performance_schema.memory_summary_by_thread_by_event_name表
根据thread和event_name显示内存的使用情况
GreatDB Cluster> select THREAD_ID, EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_by_thread_by_event_name order by COUNT_ALLOC desc limit 5;
+-----------+-------------------------------------+-------------+---------------------------+
| THREAD_ID | EVENT_NAME | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+-----------+-------------------------------------+-------------+---------------------------+
| 34 | memory/sql/Sid_map::Node | 193171 | 16997308 |
| 53 | memory/innodb/sync0rw | 120237 | 10580856 |
| 58 | memory/sql/Gtid_set::Interval_chunk | 111325 | 22264664 |
| 58 | memory/sql/Sid_map::Node | 57961 | 2858172 |
| 59 | memory/sql/Gtid_set::to_string | 57807 | 2869830 |
+-----------+-------------------------------------+-------------+---------------------------+
5 rows in set (0.04 sec)
performance_schema.memory_summary_by_user_by_event_name表
根据user和event_name显示内存的使用情况
GreatDB Cluster> select USER, EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_by_user_by_event_name order by COUNT_ALLOC desc limit 5;
+------+-------------------------------------+-------------+---------------------------+
| USER | EVENT_NAME | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+------+-------------------------------------+-------------+---------------------------+
| NULL | memory/innodb/sync0rw | 213686 | 18804368 |
| NULL | memory/sql/Sid_map::Node | 192502 | 16938320 |
| root | memory/innodb/sync0rw | 155684 | 13700192 |
| root | memory/sql/Gtid_set::Interval_chunk | 137870 | 26342488 |
| root | memory/sql/Sid_map::Node | 115540 | 5696852 |
+------+-------------------------------------+-------------+---------------------------+
5 rows in set (0.01 sec)
performance_schema.memory_summary_global_by_event_name表
根据event_name显示全局内存的使用情况
GreatDB Cluster> select EVENT_NAME, COUNT_ALLOC, SUM_NUMBER_OF_BYTES_ALLOC from performance_schema.memory_summary_global_by_event_name order by COUNT_ALLOC desc limit 6;
+---------------------------------------+-------------+---------------------------+
| EVENT_NAME | COUNT_ALLOC | SUM_NUMBER_OF_BYTES_ALLOC |
+---------------------------------------+-------------+---------------------------+
| memory/innodb/sync0rw | 370520 | 32605760 |
| memory/sql/Sid_map::Node | 310287 | 22800252 |
| memory/sql/Gtid_set::Interval_chunk | 138922 | 26542304 |
| memory/group_rpl/GCS_XCom::xcom_cache | 112561 | 54620591 |
| memory/sql/Gtid_set::to_string | 100779 | 4917058 |
| memory/sql/dd::String_type | 77358 | 34742264 |
+---------------------------------------+-------------+---------------------------+
6 rows in set (0.00 sec)




