我是许多不同客户使用的软件即服务应用程序的高级开发人员。我们的软件在由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 TABLE
,OPTIMIZE 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:我现在尝试了以下所有方法:
- 我使用相同的软件将此表和数据复制到另一个站点。这个问题并没有遵循表。它似乎仅限于这一数据库。
- 我按照gbn的答案建议更改了索引。问题仍然存在。
- 我删除了该表并重新创建为
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,但是显然有问题。