前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >MySQL最佳实践:内存管理分析

MySQL最佳实践:内存管理分析

原创
作者头像
brightdeng@DBA
修改于 2020-12-29 13:27:46
修改于 2020-12-29 13:27:46
11.9K00
代码可运行
举报
运行总次数:0
代码可运行

前言

在日常工作中,时不时会收到内存使用率高的告警,那么我们应该如何处理呢?本文将从LinuxMySQL两个层面,介绍内存管理的相关知识点,希望能给大家带来一些帮助,以便更好地应对内存问题。

如何看懂内存指标

遇到内存问题,可以先通过free、vmstat、top等命令,进行检查。free命令,可以获取系统内存的总体使用情况;vmstat命令,可以实时观察内存的变化情况;top命令,可以进行排序,获取内存占用大的进程。这里简单介绍一下free命令输出(以CentOS 7为例):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
              total        used        free      shared  buff/cache   available
Mem:        8008704     5234876      157920         640     2615908     2467292
Swap:          2047           0        2047

第一行是内存数据

  • total:内存总大小,对应于/proc/meminfo的MemTotal
  • used:已使用的内存大小,对应于/proc/meminfo的(MemTotal - MemFree - Buffers - Cached - Slab)
  • free:未使用的内存大小,对应于/proc/meminfo的MemFree
  • buff/cache:已使用的缓存大小,对应于/proc/meminfo的Buffers+Cached
  • available:可供使用的内存大小,这是一个预估值,对应于/proc/meminfo的MemAvailable

第二行是交换分区数据

  • total:交换分区总大小,对应于/proc/meminfo的SwapTotal
  • used:已使用的交换分区,对应于/proc/meminfo的(SwapTotal - SwapFree)
  • free:未使用的的内存大小,对应于/proc/meminfo的SwapFree

这里值得注意的是,Linux操作系统会最大限度利用内存,空闲内存free少,不代表系统内存不够用了。个人建议,一方面需要观察内存增长的整体趋势是否逐渐趋于平稳、以及used和buff/cache的变化情况;另一方面需要观察是否频繁使用到交换分区swap,当然了,这里要避免NUMA和swapiness设置不正确带来的干扰。

MySQL如何使用内存

在MySQL中,内存占用主要包括以下几部分,全局共享的内存、线程独占的内存、内存分配器占用的内存,具体如下:

全局共享

  • innodb_buffer_pool_size:InnoDB缓冲池的大小
  • innodb_additional_mem_pool_size:InnoDB存放数据字典和其他内部数据结构的内存大小,5.7已被移除
  • innodb_log_buffer_size:InnoDB日志缓冲的大小
  • key_buffer_size:MyISAM缓存索引块的内存大小
  • query_cache_size:查询缓冲的大小,8.0已被移除

线程独占

  • thread_stack:每个线程分配的堆栈大小
  • sort_buffer_size:排序缓冲的大小
  • join_buffer_size:连接缓冲的大小
  • read_buffer_size:MyISAM顺序读缓冲的大小
  • read_rnd_buffer_size:MyISAM随机读缓冲的大小、MRR缓冲的大小
  • tmp_table_size/max_heap_table_size:内存临时表的大小
  • binlog_cache_size:二进制日志缓冲的大小

内存分配器

在MySQL中,buffer pool的内存,是通过mmap()方式直接向操作系统申请分配;除此之外,大多数的内存管理,都需要经过内存分配器。为了实现更高效的内存管理,避免频繁的内存分配与回收,内存分配器会长时间占用大量内存,以供内部重复使用。关于内存分配器的选择,推荐使用jemalloc,可以有效解决内存碎片与提升整体性能。

因此,MySQL占用内存高的原因可能包括:innodb_buffer_pool_size设置过大、连接数/并发数过高、大量排序操作、内存分配器占用、以及MySQL Bug等等。一般来说,在MySQL整个运行周期内,刚启动时内存上涨会比较快,运行一段时间后会逐渐趋于平稳,这种情况是不需要过多关注的;如果在稳定运行后,出现内存突增、内存持续增长不释放的情况,那就需要我们进一步分析是什么原因造成的。

到底是谁占用了内存

在绝大多数情况下,我们是不需要花费过多精力,去关注MySQL内存使用情况的;但是,也不能排除确实存在内存占用异常的情况,这个时候我们应该如何去进行深入排查呢?其实,MySQL官方就提供了强大的实时监控工具——performance_schema库下的监控内存表,通过这个工具,我们可以很清晰地观察到MySQL内存到底是被谁占用了、分别占用了多少。

开启内存监控

  • 实例启动时开启

我们可以选择,在实例启动时,开启内存监控采集器,具体方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
vi my.cnf
performance-schema-instrument='memory/%=ON'

禁用方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
vi my.cnf
performance-schema-instrument='memory/%=OFF'
  • 实例运行时开启

我们也可以选择,在实例运行时,动态开启内存监控采集器,具体方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'memory/%';

禁用方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'NO' WHERE NAME LIKE 'memory/%';

因为采集器的实现原理,是在内存进行分配/回收时,更新相对应内存监控表的数据;换句话说,就是采集器只能监控到开启之后的内存使用情况;而MySQL很大一部分内存都是在实例启动时就预先分配的,因此要想准确监控实例的内存使用率,需要在实例启动时就开启内存采集器。

内存监控表

在performance_schema库下,提供多个维度的内存监控表,具体如下:

  • memory_summary_by_account_by_event_name:账号纬度的内存监控表
  • memory_summary_by_host_by_event_name:主机纬度的内存监控表
  • memory_summary_by_thread_by_event_name:线程维度的内存监控表
  • memory_summary_by_user_by_event_name:用户纬度的内存监控表
  • memory_summary_global_by_event_name:全局纬度的内存监控表

内存监控表均包括以下关键字段:

  • COUNT_ALLOC:内存分配次数
  • COUNT_FREE:内存回收次数
  • SUM_NUMBER_OF_BYTES_ALLOC:内存分配大小
  • SUM_NUMBER_OF_BYTES_FREE:内存回收大小
  • CURRENT_COUNT_USED:当前分配的内存,通过COUNT_ALLOC-COUNT_FREE计算得到
  • CURRENT_NUMBER_OF_BYTES_USED:当前分配的内存大小,通过SUM_NUMBER_OF_BYTES_ALLOC-SUM_NUMBER_OF_BYTES_FREE计算得到
  • LOW_COUNT_USED:CURRENT_COUNT_USED的最小值
  • HIGH_COUNT_USED:CURRENT_COUNT_USED的最大值
  • LOW_NUMBER_OF_BYTES_USED:CURRENT_NUMBER_OF_BYTES_USED的最小值
  • HIGH_NUMBER_OF_BYTES_USED:CURRENT_NUMBER_OF_BYTES_USED的最大值

接下来,让我们看一个正常运行实例的内存使用情况,具体如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select USER,HOST,EVENT_NAME,COUNT_ALLOC,COUNT_FREE,CURRENT_COUNT_USED,SUM_NUMBER_OF_BYTES_ALLOC,SUM_NUMBER_OF_BYTES_FREE,CURRENT_NUMBER_OF_BYTES_USED from performance_schema.memory_summary_by_account_by_event_name order by CURRENT_NUMBER_OF_BYTES_USED desc limit 10;
+------+-----------+----------------------------+-------------+------------+--------------------+---------------------------+--------------------------+------------------------------+
| USER | HOST      | EVENT_NAME                 | COUNT_ALLOC | COUNT_FREE | CURRENT_COUNT_USED | SUM_NUMBER_OF_BYTES_ALLOC | SUM_NUMBER_OF_BYTES_FREE | CURRENT_NUMBER_OF_BYTES_USED |
+------+-----------+----------------------------+-------------+------------+--------------------+---------------------------+--------------------------+------------------------------+
| NULL | NULL      | memory/innodb/buf_buf_pool |          32 |          0 |                 32 |                4500488192 |                        0 |                   4500488192 |
| NULL | NULL      | memory/innodb/os0event     |     1573559 |          0 |            1573559 |                 214004024 |                        0 |                    214004024 |
| NULL | NULL      | memory/innodb/hash0hash    |          82 |          6 |                 76 |                 397976480 |                227067024 |                    170909456 |
| NULL | NULL      | memory/innodb/log0log      |          10 |          0 |                 10 |                  33565840 |                        0 |                     33565840 |
| root | localhost | memory/innodb/std          |     3650638 |    3043111 |             607527 |                 160778066 |                141334898 |                     19443168 |
| NULL | NULL      | memory/mysys/KEY_CACHE     |           3 |          0 |                  3 |                   8390768 |                        0 |                      8390768 |
| NULL | NULL      | memory/innodb/ut0pool      |           2 |          0 |                  2 |                   4194480 |                        0 |                      4194480 |
| NULL | NULL      | memory/innodb/sync0arr     |           3 |          0 |                  3 |                   2506184 |                        0 |                      2506184 |
| NULL | NULL      | memory/innodb/lock0lock    |          33 |          0 |                 33 |                   2245040 |                        0 |                      2245040 |
| root | localhost | memory/innodb/mem0mem      |     9897784 |    9896793 |                991 |                8845389160 |               8843147749 |                      2241411 |
+------+-----------+----------------------------+-------------+------------+--------------------+---------------------------+--------------------------+------------------------------+
10 rows in set (0.01 sec)

再看一个Bug #86821的场景,buffer pool占用最大内存正常,但是存储过程占用3GB就比较异常了,存在内存泄漏的风险;由此可知,通过内存监控表,我们可以快速定位内存异常占用问题。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select event_name, current_alloc, high_alloc from memory_global_by_current_bytes where current_count > 0;
+--------------------------------------------------------------------------------+---------------+-------------+
| event_name                                                                     | current_alloc | high_alloc  |
+--------------------------------------------------------------------------------+---------------+-------------+
| memory/innodb/buf_buf_pool                                                     | 7.29 GiB      | 7.29 GiB    |
| memory/sql/sp_head::main_mem_root                                              | 3.21 GiB      | 3.62 GiB    |
| memory/innodb/hash0hash                                                        | 210.16 MiB    | 323.63 MiB  |
| memory/sql/TABLE                                                               | 183.82 MiB    | 190.28 MiB  |
| memory/sql/Query_cache                                                         | 128.02 MiB    | 128.02 MiB  |
| memory/mysys/KEY_CACHE                                                         | 64.00 MiB     | 64.00 MiB   |
| memory/innodb/log0log                                                          | 32.08 MiB     | 32.08 MiB   |
| memory/innodb/parallel_doublewrite                                             | 30.27 MiB     | 30.27 MiB   |
| memory/performance_schema/table_handles                                        | 27.19 MiB     | 27.19 MiB   |
| memory/innodb/mem0mem                                                          | 19.14 MiB     | 20.79 MiB   |
| memory/performance_schema/events_statements_history_long                       | 13.66 MiB     | 13.66 MiB   |
| memory/performance_schema/events_statements_summary_by_digest.tokens           | 9.77 MiB      | 9.77 MiB    |

另外,如果我们在内存监控表,看见一些比较陌生的event,可以翻阅官方文档或源码,继续进一步解读,例如

memory/innodb/os0event

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /** @file include/os0event.h
  The interface to the operating system condition variables
 
  Created 2012-09-23 Sunny Bains (split from os0sync.h)
  *******************************************************/

memory/innodb/hash0hash

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /** @file include/hash0hash.h
  The simple hash table utility
 
  Created 5/20/1997 Heikki Tuuri
  *******************************************************/

总结

总的来说,只要我们的操作系统/数据库有一个相对合理的配置(NUMA、swapiness、jemalloc

、innodb_buffer_pool_size等等),大多数情况是不需要关注内存问题的;如果非常不幸运地碰到内存占用异常问题,可以通过官方提供的实时监控工具——内存监控表,快速进行定位;不过需要注意的是,开启内存采集器也会带来一些问题,比如额外的内存占用和性能损耗,一般建议是在系统出现内存问题之后,再重启实例启用,并等待复现。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
ApacheCN 数据科学译文集 20210313 更新
新增了五个教程: Python 和 Jupyter 机器学习入门 零、前言 一、Jupyter 基础知识 二、数据清理和高级机器学习 三、Web 爬取和交互式可视化 Python 数据科学和机器学习实践指南 零、前言 一、入门 二、统计和概率回顾和 Python 实践 三、Matplotlib 和高级概率概念 四、预测模型 五、Python 机器学习 六、推荐系统 七、更多数据挖掘和机器学习技术 八、处理真实数据 九、Apache Spark-大数据机器学习 十、测试与实验设计 精通 Python 数据
ApacheCN_飞龙
2022/05/07
3820
黑客X档案 2008~2012 NPM、PYPI、DockerHub 备份
黑客X档案合订本2008年(上)第一部分 Docker docker pull apachecn0/heix-2008-part1 docker run -tid -p <port>:80 apachecn0/heix-2008-part1 # 访问 http://localhost:{port} 查看文档 PYPI pip install heix-2008-part1 heix-2008-part1 <port> # 访问 http://localhost:{port} 查看文档 NPM npm ins
ApacheCN_飞龙
2022/06/03
5740
上海交大软件学院课件 NPM、PYPI、DockerHub 备份
sjtu_se_101_ics1 Docker docker pull apachecn0/sjtu-se101-ics1 docker run -tid -p <port>:80 apachecn0/sjtu-se101-ics1 # 访问 http://localhost:{port} 查看文档 PYPI pip install sjtu-se101-ics1 sjtu-se101-ics1 <port> # 访问 http://localhost:{port} 查看文档 NPM npm install
ApacheCN_飞龙
2022/06/03
1.1K0
非安全系列教程 NPM、PYPI、DockerHub 备份
Hack编程实例精讲 Docker docker pull apachecn0/hack-biancheng-shili-jingjiang docker run -tid -p <port>:80 apachecn0/hack-biancheng-shili-jingjiang # 访问 http://localhost:{port} 查看文档 PYPI pip install hack-biancheng-shili-jingjiang hack-biancheng-shili-jingjiang <p
ApacheCN_飞龙
2022/05/07
1.4K0
高校计算机课件(一)NPM、PYPI、DockerHub 备份
东南大学高级数据结构课件(崇志宏) Docker docker pull apachecn0/seu-adv-dast-chongzhihong docker run -tid -p <port>:80 apachecn0/seu-adv-dast-chongzhihong # 访问 http://localhost:{port} 查看文档 PYPI pip install seu-adv-dast-chongzhihong seu-adv-dast-chongzhihong <port> # 访问 ht
ApacheCN_飞龙
2022/12/07
5330
玄学资料库(一)NPM、PYPI、DockerHub 备份
白魔法让你工作超顺利 Docker docker pull apachecn0/baimofa-rangni-gongzuo-chaoshunli docker run -tid -p <port>:80 apachecn0/baimofa-rangni-gongzuo-chaoshunli # 访问 http://localhost:{port} 查看文档 PYPI pip install baimofa-rangni-gongzuo-chaoshunli baimofa-rangni-gongzuo-c
ApacheCN_飞龙
2022/10/08
2K0
黑客防线 2007~2012 NPM、PYPI、DockerHub 备份
黑客防线2007精华本(上) Docker docker pull apachecn0/heifang-2007-jinghua-part1 docker run -tid -p <port>:80 apachecn0/heifang-2007-jinghua-part1 # 访问 http://localhost:{port} 查看文档 PYPI pip install heifang-2007-jinghua-part1 heifang-2007-jinghua-part1 <port> # 访问 ht
ApacheCN_飞龙
2022/06/03
7550
黑客防线 2000~2006 NPM、PYPI、DockerHub 备份
黑客防线2000-2001精华本 Docker docker pull apachecn0/heifang-2000-jinghua docker run -tid -p <port>:80 apachecn0/heifang-2000-jinghua # 访问 http://localhost:{port} 查看文档 PYPI pip install heifang-2000-jinghua heifang-2000-jinghua <port> # 访问 http://localhost:{port}
ApacheCN_飞龙
2022/06/03
6760
玄学资料库(二)NPM、PYPI、DockerHub 备份
爱情全占星 Docker docker pull apachecn0/aiqing-quanzhanxing docker run -tid -p <port>:80 apachecn0/aiqing-quanzhanxing # 访问 http://localhost:{port} 查看文档 PYPI pip install aiqing-quanzhanxing aiqing-quanzhanxing <port> # 访问 http://localhost:{port} 查看文档 NPM npm in
ApacheCN_飞龙
2022/10/08
1.8K0
CEH 讲义 NPM、PYPI、DockerHub 备份
CEHv10ModuleAllInOne Docker docker pull apachecn0/ceh-v10-mod-allinone docker run -tid -p <port>:80 apachecn0/ceh-v10-mod-allinone # 访问 http://localhost:{port} 查看文档 PYPI pip install ceh-v10-mod-allinone ceh-v10-mod-allinone <port> # 访问 http://localhost:{po
ApacheCN_飞龙
2022/06/03
6710
黑客防线、黑客X档案专辑 NPM、PYPI、DockerHub 备份
编程解析精粹 Docker docker pull apachecn0/biancheng-jiexi-jingcui docker run -tid -p <port>:80 apachecn0/biancheng-jiexi-jingcui # 访问 http://localhost:{port} 查看文档 PYPI pip install biancheng-jiexi-jingcui biancheng-jiexi-jingcui <port> # 访问 http://localhost:{port
ApacheCN_飞龙
2022/06/03
1.1K0
达内 Java 全套教程 NPM、PYPI、DockerHub 备
达内AJAX和JQuery_扫描版_2.11M Docker docker pull apachecn0/tarena-ajax-jquery docker run -tid -p <port>:80 apachecn0/tarena-ajax-jquery # 访问 http://localhost:{port} 查看文档 PYPI pip install tarena-ajax-jquery tarena-ajax-jquery <port> # 访问 http://localhost:{port} 查
ApacheCN_飞龙
2022/06/03
1.2K0
TutorialGateway BI 中文系列教程【翻译完成】
原文:TutorialGateway 协议:CC BY-NC-SA 4.0 阶段:机翻(1) 危机只有发展到最困难的阶段,才有可能倒逼出有效的解决方案。——《两次全球大危机的比较研究》 在线阅读 在线阅读(Gitee) ApacheCN 学习资源 目录 Talend Tableau PowerBI SSIS SSRS SSAS MDX R 语言教程 Alteryx QlikView 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 请您勇敢地去翻译和改进翻译。虽然我们追求卓越,但
ApacheCN_飞龙
2022/04/02
2250
StudyTonight 中文系列教程【翻译完成】
原文:StudyTonight 协议:CC BY-NC-SA 4.0 人最大的痛苦就是说一些自己都不相信的话。——燕京学堂鹿会 在线阅读 在线阅读(Gitee) ApacheCN 学习资源 目录 C/C++ 中文教程 Python 中文教程 Web 中文教程 Spring 中文教程 Java 中文教程 计算机系统中文教程 数据库中文教程 移动开发中文教程 杂项中文教程 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 请您勇敢地去翻译和改进翻译。虽然我们追求卓越,但我们并不要求您做
ApacheCN_飞龙
2022/04/02
1860
JavaTPoint 移动开发教程【翻译完成】
原文:JavaTPoint 协议:CC BY-NC-SA 4.0 阶段:机翻(1) 要打多久,就打多久,一直打到完全胜利!——教员 在线阅读 在线阅读(Gitee) ApacheCN 学习资源 目录 Kotlin 教程 Kotlin 安卓教程 Swift 教程 移动通信 Xamarin 教程 Ionic 教程 Flutter 教程 Gradle 教程 PhoneGap 教程 Dart 教程 ApacheCordova 物联网 Arduino 教程 PLC 教程 iOS 开发 PWA 教程 厄拉多塞的
ApacheCN_飞龙
2022/04/02
2130
JavaTPoint 数据库教程【翻译完成】
原文:JavaTPoint 协议:CC BY-NC-SA 4.0 阶段:机翻(1) 危机只有发展到最困难的阶段,才有可能倒逼出有效的解决方案。——《两次全球大危机的比较研究》 在线阅读 在线阅读(Gitee) ApacheCN 学习资源 目录 SQL 教程 PL/SQL 教程 MySQL 教程 MongoDB 教程 PostgreSQL 教程 SQL Server 教程 Oracle 教程 Cassandra 教程 SQLite 教程 Neo4j 教程 CouchDB 教程 MariaDB 教程 D
ApacheCN_飞龙
2022/04/02
3960
JavaTPoint 计算机科学教程【翻译完成】
原文:JavaTPoint 协议:CC BY-NC-SA 4.0 阶段:机翻(1) 危机只有发展到最困难的阶段,才有可能倒逼出有效的解决方案。——《两次全球大危机的比较研究》 在线阅读 在线阅读(Gitee) ApacheCN 学习资源 目录 机器人教程 模糊逻辑教程 数字电子学 图论教程 微处理器教程 DS 教程 DAA 教程 操作系统教程 计算机网络 编译器教程 COA 教程 离散数学 软件工程 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 请您勇敢地去翻译和改进翻译。虽
ApacheCN_飞龙
2022/04/02
3090
JavaTPoint 大数据和云计算中文教程【翻译完成】
原文:JavaTPoint 协议:CC BY-NC-SA 4.0 阶段:机翻(1) 危机只有发展到最困难的阶段,才有可能倒逼出有效的解决方案。——《两次全球大危机的比较研究》 在线阅读 在线阅读(Gitee) ApacheCN 学习资源 目录 Hadoop 教程 HBase 教程 Hive 教程 Sqoop PIG 教程 Spark 教程 Kafka 教程 Solr 教程 Dialogflow 教程 PySpark 教程 Apache NiFi 教程 OpenStack 教程 Kibana 教程 K
ApacheCN_飞龙
2022/04/02
2170
TutorialGateway 中文系列教程【翻译完成】
原文:TutorialGateway 协议:CC BY-NC-SA 4.0 阶段:机翻(1) 以斗争求团结则团结存,以妥协求团结则团结亡。——教员 在线阅读 在线阅读(Gitee) ApacheCN 学习资源 目录 C C# Python SQL Java JS MySQL C 语言示例 C++ 示例 Go 示例 Python 示例 Java 示例 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 请您勇敢地去翻译和改进翻译。虽然我们追求卓越,但我们并不要求您做到十全十美,因此请
ApacheCN_飞龙
2022/04/02
2390
JavaTPoint 工具中文教程【翻译完成】
原文:JavaTPoint 协议:CC BY-NC-SA 4.0 阶段:机翻(1) 危机只有发展到最困难的阶段,才有可能倒逼出有效的解决方案。——《两次全球大危机的比较研究》 在线阅读 在线阅读(Gitee) ApacheCN 学习资源 目录 Ubuntu 上的安装 苹果操作系统中的软件安装 在 CentOS 上安装 nginx 教程 Bash 教程 Git 教程 LATEX 教程 SVN 教程 辅助教程 Firebase 教程 UML 教程 Tally 教程 Kubernetes 教程 Puppe
ApacheCN_飞龙
2022/04/02
2690
推荐阅读
相关推荐
ApacheCN 数据科学译文集 20210313 更新
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验