是什么导致PHP和MySQL之间奇怪的查询超时?


11

我是许多不同客户使用的软件即服务应用程序的高级开发人员。我们的软件在由MySQL后端提供支持的Apache / PHP应用服务器集群上运行。在该软件的一个特定实例上,当客户拥有29个以上类别时,用于查询类别名称列表的PHP代码将超时。我知道这没有道理;没有什么特别的数字可以打破这个数字30,而其他客户拥有超过30个类别,但是,问题是当这个安装具有30个或更多类别时,它是100%可复制的,而当类别少于30时,这个问题就消失了。

有问题的表是:

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(64) NOT NULL,
  `title` varchar(128) NOT NULL,
  `parent` int(10) unsigned NOT NULL,
  `keywords` varchar(255) NOT NULL,
  `description` text NOT NULL,
  `status` enum('Active','Inactive','_Deleted','_New') NOT NULL default 'Active',
  `style` enum('_Unknown') default NULL COMMENT 'Autoenum;',
  `order` smallint(5) unsigned NOT NULL,
  `created_at` datetime NOT NULL,
  `modified_at` datetime default NULL,
  PRIMARY KEY  (`id`),
  KEY `name` (`name`),
  KEY `parent` (`parent`),
  KEY `created_at` (`created_at`),
  KEY `modified_at` (`modified_at`),
  KEY `status` (`status`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COMMENT='R2' AUTO_INCREMENT=33 ;

有问题的代码递归查询表以获取所有类别。它发出一个

SELECT * FROM `categories` WHERE `parent`=0 ORDER BY `order`,`name`

然后针对返回的每一行重复此查询,但WHERE parent=$category_id每次使用。(我确定可以改进此过程,但这可能是另一个问题)

据我所知,以下查询将永远挂起:

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

我可以在服务器上的mysql客户端中很好地执行此查询,也可以在PHPMyAdmin中执行它,而不会出现问题。

请注意,不是特定的查询才是问题所在。如果DELETE FROM categories WHERE id=22然后我将挂起与上面类似的另一个查询。另外,当我手动运行它时,上面的查询返回零行

我怀疑该表可能已损坏,因此我尝试了这些报告的问题REPAIR TABLEOPTIMIZE TABLE但是并没有解决该问题。我放下表并重新创建,但是问题又回来了。这与其他客户使用的表结构和PHP代码完全相同,而其他任何人(包括拥有30多个类别的客户)都不会遇到任何问题。

PHP代码不会永远递归。(这不是无限循环)

MySQL服务器正在i686上运行带有PC linux-gnu的mysqld Ver 5.0.92-community的CentOS linux(MySQL Community Edition(GPL))

MySQL服务器上的负载较低:平均负载:0.58、0.75、0.73,CPU:4.6%us,2.9%sy,0.0%ni,92.2%id,0.0%wa,0.0%hi,0.3%si, 0.0%st。正在使用的交换可以忽略不计(448k)

如何解决此问题?关于可能发生的事情有什么建议吗?

更新:TRUNCE编辑了表并插入了30行虚拟数据:

INSERT INTO `categories` (`id`, `name`, `title`, `parent`, `keywords`, `description`, `status`, `style`, `order`, `created_at`, `modified_at`) VALUES
(1, 'New Category', '', 0, '', '', 'Inactive', NULL, 1, '2011-10-25 12:06:30', '2011-10-25 12:06:34'),
(2, 'New Category', '', 0, '', '', 'Inactive', NULL, 2, '2011-10-25 12:06:39', '2011-10-25 12:06:40'),
(3, 'New Category', '', 0, '', '', 'Inactive', NULL, 3, '2011-10-25 12:06:41', '2011-10-25 12:06:42'),
(4, 'New Category', '', 0, '', '', 'Inactive', NULL, 4, '2011-10-25 12:06:46', '2011-10-25 12:06:47'),
(5, 'New Category', '', 0, '', '', 'Inactive', NULL, 5, '2011-10-25 12:06:49', NULL),
(6, 'New Category', '', 0, '', '', 'Inactive', NULL, 6, '2011-10-25 12:06:51', '2011-10-25 12:06:52'),
(7, 'New Category', '', 0, '', '', 'Inactive', NULL, 7, '2011-10-25 12:06:53', '2011-10-25 12:06:54'),
(8, 'New Category', '', 0, '', '', 'Inactive', NULL, 8, '2011-10-25 12:06:56', '2011-10-25 12:06:57'),
(9, 'New Category', '', 0, '', '', 'Inactive', NULL, 9, '2011-10-25 12:06:59', '2011-10-25 12:06:59'),
(10, 'New Category', '', 0, '', '', 'Inactive', NULL, 10, '2011-10-25 12:07:01', '2011-10-25 12:07:01'),
(11, 'New Category', '', 0, '', '', 'Inactive', NULL, 11, '2011-10-25 12:07:03', '2011-10-25 12:07:03'),
(12, 'New Category', '', 0, '', '', 'Inactive', NULL, 12, '2011-10-25 12:07:05', '2011-10-25 12:07:05'),
(13, 'New Category', '', 0, '', '', 'Inactive', NULL, 13, '2011-10-25 12:07:06', '2011-10-25 12:07:07'),
(14, 'New Category', '', 0, '', '', 'Inactive', NULL, 14, '2011-10-25 12:07:08', '2011-10-25 12:07:09'),
(15, 'New Category', '', 0, '', '', 'Inactive', NULL, 15, '2011-10-25 12:07:11', '2011-10-25 12:07:12'),
(16, 'New Category', '', 0, '', '', 'Inactive', NULL, 16, '2011-10-25 12:07:13', '2011-10-25 12:07:14'),
(17, 'New Category', '', 0, '', '', 'Inactive', NULL, 17, '2011-10-25 12:09:41', '2011-10-25 12:09:42'),
(18, 'New Category', '', 0, '', '', 'Inactive', NULL, 18, '2011-10-25 12:09:47', NULL),
(19, 'New Category', '', 0, '', '', 'Inactive', NULL, 19, '2011-10-25 12:09:48', NULL),
(20, 'New Category', '', 0, '', '', 'Inactive', NULL, 20, '2011-10-25 12:09:48', NULL),
(21, 'New Category', '', 0, '', '', 'Inactive', NULL, 21, '2011-10-25 12:09:49', NULL),
(22, 'New Category', '', 0, '', '', 'Inactive', NULL, 22, '2011-10-25 12:09:50', NULL),
(23, 'New Category', '', 0, '', '', 'Inactive', NULL, 23, '2011-10-25 12:09:51', NULL),
(24, 'New Category', '', 0, '', '', 'Inactive', NULL, 24, '2011-10-25 12:09:51', NULL),
(25, 'New Category', '', 0, '', '', 'Inactive', NULL, 25, '2011-10-25 12:09:52', NULL),
(26, 'New Category', '', 0, '', '', 'Inactive', NULL, 26, '2011-10-25 12:09:53', NULL),
(27, 'New Category', '', 0, '', '', 'Inactive', NULL, 27, '2011-10-25 12:09:54', NULL),
(28, 'New Category', '', 0, '', '', 'Inactive', NULL, 28, '2011-10-25 12:09:55', NULL),
(29, 'New Category', '', 0, '', '', 'Inactive', NULL, 29, '2011-10-25 12:09:56', NULL),
(30, 'New Category', '', 0, '', '', 'Inactive', NULL, 30, '2011-10-25 12:09:57', NULL);

根本没有父母,所有类别都在顶层。问题仍然存在。由PHP执行的以下查询失败:

SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`

这是EXPLAIN

mysql> EXPLAIN SELECT * FROM `categories` WHERE `parent`=22 ORDER BY `order`,`name`;
+----+-------------+------------+------+---------------+--------+---------+-------+------+-----------------------------+
| id | select_type | table      | type | possible_keys | key    | key_len | ref   | rows | Extra                       |
+----+-------------+------------+------+---------------+--------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | categories | ref  | parent        | parent | 4       | const |    1 | Using where; Using filesort | 
+----+-------------+------------+------+---------------+--------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)

更新2:我现在尝试了以下所有方法:

  1. 我使用相同的软件将此表和数据复制到另一个站点。这个问题并没有遵循表。它似乎仅限于这一数据库。
  2. 我按照gbn的答案建议更改了索引。问题仍然存在。
  3. 我删除了该表并重新创建为InnoDB表,并在上面插入了相同的30条测试行。问题仍然存在。

我怀疑这个数据库一定有问题...

更新#3:我完全删除了数据库,并以新名称重新创建了数据库,并导入了她的数据。问题仍然存在。

我发现实际挂起的PHP语句是对的调用mysql_query()。此后的语句永远不会执行。

当该调用挂起时, MySQL将线程列为睡眠状态!

mysql> show full processlist;
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
| Id    | User             | Host                        | db                   | Command | Time | State | Info                  |
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+
|  5560 | root             | localhost                   | problem_db           | Query   |    0 | NULL  | show full processlist |  
                          ----- many rows which have no relevancy; only rows from this customer's app are shown ------
| 16341 | shared_db        | oak01.sitepalette.com:53237 | shared_db            | Sleep   |  308 |       | NULL                  | 
| 16342 | problem_db       | oak01.sitepalette.com:60716 | problem_db           | Sleep   |  307 |       | NULL                  | 
| 16344 | shared_db        | oak01.sitepalette.com:53241 | shared_db            | Sleep   |  308 |       | NULL                  | 
| 16346 | problem_db       | oak01.sitepalette.com:60720 | problem_db           | Sleep   |  308 |       | NULL                  |  
+-------+------------------+-----------------------------+----------------------+---------+------+-------+-----------------------+

更新#4:我已将其范围缩小为两个表的组合,即categories上面详述的media_images表和具有556行的表。如果该media_images表包含少于556行,或者该categories表包含少于30行,那么问题就消失了。就像我在这里遇到的某种MySQL限制...

更新#5:我只是试图将数据库完全移到另一个MySQL服务器上,问题就消失了……所以这与我的生产数据库服务器有关……

更新#6:这是每次都挂起的相关PHP代码:

    public function find($type,$conditions='',$order='',$limit='')
    {
            if($this->_link == self::AUTO_LINK)
                    $this->_link = DFStdLib::database_connect();

            if(is_resource($this->_link))
            {
                    $q = "SELECT ".($type==_COUNT?'COUNT(*)':'*')." FROM `{$this->_table}`";
                    if($conditions)
                    {
                            $q .= " WHERE $conditions";
                    }
                    if($order)
                    {
                            $q .= " ORDER BY $order";
                    }
                    if($limit)
                    {
                            $q .= " LIMIT $limit";
                    }

                    switch($type)
                    {
                            case _ALL:
                                    DFSkel::log(DFSkel::LOG_DEBUG,"mysql_query($q,$this->_link);");
                                    $res = @mysql_query($q,$this->_link);
                                    DFSkel::log(DFSkel::LOG_DEBUG,"res = $res");

此代码是在生产和工作上的所有其他安装。仅安装一次,它就挂在$res = @mysql_query($q,$this->_link);。我知道,因为我mysql_query在调试日志中看到,而不是 res =,并且当我strace在PHP进程中时,它挂在read(

更新#whatever-it-is-I-hate- this -&(#^&- issue!现在我的两个客户都开始发生这种情况。我只是解雇了tcpdump看来 MySQL的响应从未完全发送出去。 TCP流似乎在发送完整的MySQL响应之前就挂起了(不过我仍在调查中)

更新#我已经完全疯狂了,但是现在可以正常工作了:好的,这没有道理,但是我找到了解决方案。如果我将第二个IP地址分配给MySQL服务器的eth2接口,并将一个IP用于NFS通信,将第二个IP用于MySQL,那么问题就消失了。就像我在某种程度上...如果两个NFS + MySQL流量都都转到该IP,则会使Ip地址超载。但这意义非零,因为您不能“超载” IP地址。当然可以饱和接口,但是它是相同的接口。

知道这里到底发生了什么吗?这可能是一个unix.SE或ServerFault问题...(至少现在可以使用...)

更新#why-oh-why:此问题仍在发生。即使使用两个不同的IP,它也开始发生。我可以继续创建新的私有IP,但是显然有问题。


好了,这是一个潜在的“其他问题”的链接,该问题涉及在mysql中全部进行递归层次查询。
德里克·唐尼

@DTest当然,我稍后会补充说明。感谢其他链接!
乔什(Josh)

我们正在积极尝试在聊天中为发现此问题的任何人解决此问题。
乔什(Josh)

嗨,乔希。您说查询正常在MySQL客户端和PHPMyAdmin中运行吗?只有PHP应用程序挂出?
marcio

@marcioAlmada是的,这是正确的。我对这整个情况感到非常困惑。
乔什(Josh)

Answers:


5

对于查询计划中到底发生了什么的常规分析,可以尝试使用PROFILING

基本上,它将帮助您确定挂断的位置。

当然,仅当您使用编译了MySQL时,它才有效enable-profiling


3

想法(虽然不确定是否适用于MyISAM,但我使用的是InnoDB)

更改索引“父级”,使其位于3列上:父级,订单,名称。这与WHERE .. ORDER BY匹配

删除SELECT *。只取您需要的列。将任何其他列添加到索引“父级”

这将使优化器 使用索引,因为它现在正在覆盖。就目前而言,您必须读取整个表,因为索引对于该查询没有用


parent索引更改为(parent, order, name)
Josh

3

我会在生产数据库服务器上检查几件事

  • 检查#1:确保安装/ var / lib / mysql的数据卷没有坏块。这可能需要停机才能执行fsck(文件系统检查)
  • 检查#2:确保表不包含DML(INSERT / UPDATE / DELETE)或SELECTs
    • 在MyISAM下,将为每个DML语句发出全表锁
    • 在InnoDB下,会生成大量用于事务隔离的MVCC数据,以及集群索引锁定轻微威胁
  • 检查#3:确保PHP 正确发布了mysql_close(),并且该应用程序不依赖Apache为您关闭数据库连接。否则,当PHP尝试使用被MySQL有效关闭的数据库连接资源时,您可能会遇到某种竞争状况。
  • 检查#4:确保DB Server的操作系统的连接的netstat列表中没有TIME_WAIT的库存,这些连接在PHP和MySQL看来是关闭的,但该操作系统仍在继续。你可以看到这个netstat | grep -i mysql | grep TIME_WAIT
  • 检查#5:确保您没有使用mysql_pconnect关于持久连接未正确关闭的问题,仍然存在未解决的错误报告。我不敢想象尝试访问这些连接。
  • 检查#6:确保通过负载均衡器,交换机,防火墙和DNS服务器的数据库流量与生产数据库服务器和其他外部服务器相同。我个人讨厌在mysql.user和mysql.db的主机列中使用DNS名称。我通常会有客户将其剥离,并替换为硬IP。我还添加skip-host-cacheskip-name-resolve绕过mysqld对DNS的使用。因此,我可以将@marcioAlmada的答案作为要检查的检查点。

如果您认为这些检查都没有用,请尽快发表评论并告知我,以便我删除答案。


我绝对认为这是一个有用的答案!我不能肯定我将结束所有连接,这样我就可以尝试。我认为/var没有任何坏块(在RAID10上),但我很容易错了。我将检查netstat,那里是个好主意!我没有使用,mysql_pconnect但会检查network / dns / etc。
乔什(Josh)

@Josh:如果您看到坏块,则会在中收到很多有关它们的消息dmesg。除非您具有硬件RAID,否则请检查硬件RAID监视程序。
derobert

发生这种情况时,有时(但并非总是)我会看到一个TIME_WAITMySQL连接。无论如何,数量不多...活动并不繁重。
乔什(Josh)

2

a)乔希。您说查询正常在MySQL客户端和PHPMyAdmin中运行吗?只有PHP应用程序挂出?
b)@marcioAlmada是的,没错

我想说你已经击中了schrödinbug。您可能会尝试die()在查询之后或之前,并尝试浏览if statements很少发生的代码。当我们没有您的代码时,很难说挂了什么。

编辑:我目前说这可能是这一行

$this->_link = DFStdLib::database_connect();

每次调用函数(我假设)都会创建连接。那可能就是问题所在。您在my.cnf中的max_connections是什么?


我确切地知道它的挂起位置:它永远不会打来电话mysql_query()
乔什,乔什

1
您可以发布+-10行代码吗?

完成。我将tcpdump 在接下来的几天中对此进行调试。如果这确实 PHP问题,那么我应该在SO上发布一个新问题。
乔什(Josh)

@Josh:更新了我的答案
起源

感谢@genesis ...但是不是,有两个原因。1.仅当我使用“自动建立数据库链接”功能时才调用该代码,这是通过将其设置$this->_link为常量来完成的:self::AUTO_LINK2.即使是我,该代码也位于if:中if($this->_link == self::AUTO_LINK),并且下一行$this->_link = DFStdLib::database_connect();更改的值,$this->_link因此if不会再次运行。我确定每个线程只有一个与数据库的连接。(请参阅过程列表)
乔什(Josh

1

我几乎确信这是PHP问题,而不是MySQL问题,但是为什么在切换MySQL服务器时它可以工作?

一些尝试:

  • 防火墙?是否有防火墙阻止您的应用程序并阻止它向生产数据库服务器发出请求,反之亦然?

  • 您在连接配置中使用域名还是IP地址?使用域名可能会减慢数据库交互的速度,这与较短的PHP最大脚本执行时间相结合会导致永久的环聊

最后一个建议似乎可以解释切换数据库服务器时奇怪的变量行为。一个响应可能比另一个响应快得多,并且由于对于每个找到的记录,您都会有一个辅助查询,该假设可以解释为什么应用程序仅在一定数量的查询结果(> 30)下才延迟。

至少我们得出一个主要结论。绝对问题不在于MySQL服务器istelf。看了看文档,似乎没有适合您特定情况的功能限制,而且递归表和特定条目数也没有问题。

希望能有所帮助。


0

您是否尝试过将mysql_query()命令更新为本地PHP5驱动程序?mysqli :: query()?不知道这会做什么,但可能值得一试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.