ThinkPHP框架show columns引发mysql性能问题

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: ThinkPHP框架的show columns引发mysql性能问题,结尾有关闭方式。

一、概述

在复杂多样的云数据库使用背景下,上游应用框架百花齐放,连接数据库的方式越来越多,这导致遇到的问题也是千奇百怪;因此相较于传统dba来说,云上dba在深耕技术深度的同时还需要横向拓展多种数据库技术栈、以及简单代码能力来剖析疑难,攻克一个又一个问题。

作为一个有着长期云上数据库运维“老鸟”,在这里分享一个php代码框架中埋的巨坑!近期也是遇到两个大客户反馈,并且长时间影响核心业务,希望大家对php框架默认行为有所了解,在对客户有php应用的时候,能提醒到php中埋的大雷。

二、案例分享

Case 1:某公司2024-02-10反馈核心业务不可用

分析

step 1  看监控:只看监控来看业务量是有变化的,数据库不可用可能跟业务量有关。

现象:2024-02-10 9:30左右业务连接开始上涨,每秒新建连接数超过1000+,cpu波幅不大

image.png

step 2:查会话:客户既然说数据库慢,那么慢查、会话管理(processlist)是一定需要看的。

这里查看会话现象就能看到非常关键的两个会话状态:opening tablesremoving tmp table,会话执行的sql都是show columns from <table_name>

image.png

image.png

step 3:pstack看堆栈

看到堆栈热点主要是内部临时表相关,新建连接都是在执行show columns创建内存临时表,和客户的业务行为是正匹配的。

create_tmp_table -> instantiate_tmp_table ,主要是临时表过程中出现的问题

解问题:show columns在php程序上关闭了这个行为,后续没有再复现。

总结

问题开始是有业务突增的情况,但是实例cpu、iops等负载没有明显打高,新建连接创建内存临时表导致的,大量堆积会话在做“show columns”,扁鹊中能看到创建内存临时表热点;最后和用户确认到业务代码用到thinkphp框架,默认每次查询操作都会执行show columns操作;“show columns”操作过多导致实例在处理tmp table过程出现了性能瓶颈;

Case 2:某公司切换到polar-M之后出现性能问题

分析

step 1  看监控:业务量是有变化的,但是连接数是成倍的堆积上升。

现象:2024-06-5 12:49左右业务连接开始上涨,活跃链接从10+堆积到50+,cpu增幅20%+

image.png

step 2:查洞察:当时客户是开了sql洞察,大量会话执行慢的会话都是在show columns ,执行时间都是在几秒+

有了之前有处理过show columns问题经验,怀疑客户用的php框架(和客户确认后果然是老版本php框架)


查扁鹊:

看到热点仍然在instantiate_tmp_table创建,是和客户thinkphp框架show columns行为匹配的。

热点仍然在instantiate_tmp_table创建

结论

与case是一致的,thinkphp框架的默认show columns行为的导致,在业务量稍有变化会导致“雪崩”,实例夯死。

深入思考

mysql 源码分析

一句话共性分析:两个case都是mysql内核,在业务量稍微变化的前提下,show columns 语句引发的数据库雪崩问题;

Q1:为什么show columns 语句会导致数据库雪崩?

Some query conditions prevent the use of an in-memory temporary table, in which case the server uses an on-disk table instead:

  • The SHOW COLUMNS and DESCRIBE statements use BLOB as the type for some columns, thus the temporary table used for the results is an on-disk table.

5.7官方文档中说明show columns语句生成是blob字段,所以内部临时表是会使用到物理磁盘临时表。

从扁鹊堆栈调用来看,热点调用堆栈show columns语句会调用instantiate_tmp_table函数生成内部临时表,大量物理磁盘临时表创建导致堵塞,最终引发性能问题。

Q2:既然是内部表创建导致性能问题,是否能通过调参进行优化?

  • 默认internal_tmp_disk_storage_engine 设置为innodb,调整参数能有缓解?
  • case1中 内部临时表为innodb,case 2 中polardb 参数是 myisam;show columns常见的就是磁盘临时表,并且所以是没有办法通过调整内部临时表的存储引擎来解决;
  • 是否能通过调整tmp_table_size参数来优化?
  • 当然也是不行的,因为tmp_table_size是限制的内存临时表大小,show columns还是语句创建的磁盘临时表是无法限制的。

The maximum size of internal in-memory temporary tables. This variable does not apply to user-created MEMORY tables.

所以这个问题就是一个典型的客户行为问题,调优数据库参数是治标不治本,还是要去协调客户去优化php的行为。

Thinkphp源码分析

thinkphp getFields函数会执行"'SHOW COLUMNS FROM",具体调用路径是autoCheckField-->_checkTableInfo-->flush()-->getFields()

#getFields
/**
     * 取得数据表的字段信息
     * @access public
     */
public function getFields($tableName)
  {
    $this->initConnect(true);
    list($tableName) = explode(' ', $tableName);
    if (strpos($tableName, '.')) {
      list($dbName, $tableName) = explode('.', $tableName);
      $sql                      = 'SHOW COLUMNS FROM `' . $dbName . '`.`' . $tableName . '`';
    } else {
      $sql = 'SHOW COLUMNS FROM `' . $tableName . '`';
    }
#_checkTableInfo()
    /**
     * 自动检测数据表信息
     * @access protected
     * @return void
     */
    protected function _checkTableInfo()
    {
        // 如果不是Model类 自动记录数据表信息
        // 只在第一次执行记录
        if (empty($this->fields)) {
            // 如果数据表字段没有定义则自动获取
            if (C('DB_FIELDS_CACHE')) {
                $db     = $this->dbName ?: C('DB_NAME');
                $fields = F('_fields/' . strtolower($db . '.' . $this->tablePrefix . $this->name));
                if ($fields) {
                    $this->fields = $fields;
                    if (!empty($fields['_pk'])) {
                        $this->pk = $fields['_pk'];
                    }
                    return;
                }
            }
            // 每次都会读取数据表信息
            $this->flush();
        }
    }
# flush()
   /**
     * 获取字段信息并缓存
     * @access public
     * @return void
     */
    public function flush()
    {
        // 缓存不存在则查询数据表信息
        $this->db->setModel($this->name);
        $fields = $this->db->getFields($this->getTableName());
        if (!$fields) {
            // 无法获取字段信息
            return false;
        }
        $this->fields = array_keys($fields);
        unset($this->fields['_pk']);
        foreach ($fields as $key => $val) {
            // 记录字段类型
            $type[$key] = $val['type'];
            if ($val['primary']) {
                // 增加复合主键支持
                if (isset($this->fields['_pk']) && null != $this->fields['_pk']) {
                    if (is_string($this->fields['_pk'])) {
                        $this->pk            = array($this->fields['_pk']);
                        $this->fields['_pk'] = $this->pk;
                    }
                    $this->pk[]            = $key;
                    $this->fields['_pk'][] = $key;
                } else {
                    $this->pk            = $key;
                    $this->fields['_pk'] = $key;
                }
                if ($val['autoinc']) {
                    $this->autoinc = true;
                }
            }
        }
        // 记录字段类型信息
        $this->fields['_type'] = $type;
        // 2008-3-7 增加缓存开关控制
        if (C('DB_FIELDS_CACHE')) {
            // 永久缓存数据表信息
            $db = $this->dbName ?: C('DB_NAME');
            F('_fields/' . strtolower($db . '.' . $this->tablePrefix . $this->name), $this->fields);
        }
    }

Q1:为什么Thinkphp会执行show columns?什么情况会执行?

从源码来看,thinkphp是在每次执行DML语句的时候,都会执行show columns的语句;好家伙,php本来就是短连接模型,没有连接池,也就是每个请求在执行sql前都会show columns,也就能理解稍微业务有业务上涨,就扛不住了?

Q2:如果关闭 or 减少  show columns 语句执行?

调用路径:autoCheckField-->_checkTableInfo-->flush()-->getFields()

也就是每次会检查表字段是否有缓存,只需要提前生成字段缓存就好了;

thinkphp源码分析结论:

  • 生成字段缓存是thinkphp默认行为,本身无法关闭
  • 调试模式下会每次重新加载缓存(show columns),会导致执行频率大大增加,所以得关闭调试模式。
  • 提前手动加载字段缓存,runtime/schema 目录下面按照数据表生成字段缓存文件

建议与总结

该问题是一个典型thinkphp框架使用mysql风险隐患,在这里引出分享一些mysql内部临时表相关知识点,大家在深入客户业务架构同时,检查风险or快速解决。

步骤1:关闭调试模式
#app.php
'app_debug' => false,
database.php
'debug'           => false,
步骤2:手动加载缓存文件,可以在runtime/schema检查问阿金是否生产。
php think optimize:schema


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5月前
|
缓存 关系型数据库 MySQL
MySQL索引策略与查询性能调优实战
在实际应用中,需要根据具体的业务需求和查询模式,综合运用索引策略和查询性能调优方法,不断地测试和优化,以提高MySQL数据库的查询性能。
472 66
|
1月前
|
关系型数据库 MySQL OLAP
无缝集成 MySQL,解锁秒级 OLAP 分析性能极限,完成任务可领取三合一数据线!
通过 AnalyticDB MySQL 版、DMS、DTS 和 RDS MySQL 版协同工作,解决大规模业务数据统计难题,参与活动完成任务即可领取三合一数据线(限量200个),还有机会抽取蓝牙音箱大奖!
|
3月前
|
Cloud Native 关系型数据库 MySQL
无缝集成 MySQL,解锁秒级数据分析性能极限
在数据驱动决策的时代,一款性能卓越的数据分析引擎不仅能提供高效的数据支撑,同时也解决了传统 OLTP 在数据分析时面临的查询性能瓶颈、数据不一致等挑战。本文将介绍通过 AnalyticDB MySQL + DTS 来解决 MySQL 的数据分析性能问题。
|
3月前
|
缓存 关系型数据库 MySQL
【深入了解MySQL】优化查询性能与数据库设计的深度总结
本文详细介绍了MySQL查询优化和数据库设计技巧,涵盖基础优化、高级技巧及性能监控。
928 0
|
5月前
|
SQL 关系型数据库 MySQL
MySQL性能探究:count(*)与count(1)的性能对决
在MySQL数据库的性能优化中,对查询语句的细微差别有着深入的理解是非常重要的。`count(*)`和`count(1)`是两种常用的聚合函数,用于计算行数。在面试中,面试官经常会问到这两种函数的性能差异。本文将探讨`count(*)`与`count(1)`的性能对比,并整理十道经典的MySQL面试题,帮助你在面试中游刃有余。
219 3
|
5月前
|
缓存 监控 关系型数据库
如何根据监控结果调整 MySQL 数据库的参数以提高性能?
【10月更文挑战第28天】根据MySQL数据库的监控结果来调整参数以提高性能,需要综合考虑多个方面的因素
206 1
|
5月前
|
监控 关系型数据库 MySQL
如何监控和诊断 MySQL 数据库的性能问题?
【10月更文挑战第28天】监控和诊断MySQL数据库的性能问题是确保数据库高效稳定运行的关键
794 1
|
5月前
|
缓存 关系型数据库 MySQL
如何优化 MySQL 数据库的性能?
【10月更文挑战第28天】
335 1
|
5月前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
925 1
|
6月前
|
SQL Java 关系型数据库
java连接mysql查询数据(基础版,无框架)
【10月更文挑战第12天】该示例展示了如何使用Java通过JDBC连接MySQL数据库并查询数据。首先在项目中引入`mysql-connector-java`依赖,然后通过`JdbcUtil`类中的`main`方法实现数据库连接、执行SQL查询及结果处理,最后关闭相关资源。
555 6

相关产品

  • 云数据库 RDS MySQL 版
  • 下一篇
    oss创建bucket