MySQL查询使网站陷入瘫痪

时间:2010-05-28 17:19:30

标签: mysql optimization

偶尔会有一段时间,我们的网站会完全瘫痪。

查看SHOW FULL PROCESSLIST;,我注意到,当发生这种情况时,有一个特定的查询“Copying to tmp table”表示一段时间(有时是350秒),几乎所有其他查询是“Locked”。

我不明白的部分是90%的时间,这个查询运行正常。我看到它在进程列表中经历,并且大部分时间都很快完成。 我们的主页上的ajax调用正在调用此查询,以根据您的浏览历史记录显示产品推荐(la amazon)。

有时,随机(但经常),它会卡在“复制到tmp表”。

这是我查看时查询的一个被捕获的实例,上升了109秒:

SELECT DISTINCT product_product.id, product_product.name, product_product.retailprice, product_product.imageurl, product_product.thumbnailurl,   product_product.msrp
FROM product_product, product_xref, product_viewhistory
WHERE
(
(product_viewhistory.productId = product_xref.product_id_1 AND product_xref.product_id_2 = product_product.id)
OR
(product_viewhistory.productId = product_xref.product_id_2 AND product_xref.product_id_1 = product_product.id)
)
AND product_product.outofstock='N'
AND product_viewhistory.cookieId = '188af1efad392c2adf82'
AND product_viewhistory.productId IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172)
ORDER BY product_xref.hits DESC
LIMIT 10

当然,“cookieId”和“productId”列表会根据请求动态变化。

我在PDO中使用php。

编辑:我认为所涉及的一些表结构可能会有所帮助:

CREATE TABLE IF NOT EXISTS `product_viewhistory` (
  `userId` int(10) unsigned NOT NULL default '0',
  `cookieId` varchar(30) collate utf8_unicode_ci NOT NULL,
  `productId` int(11) NOT NULL,
  `viewTime` timestamp NOT NULL default CURRENT_TIMESTAMP,
  KEY `userId` (`userId`),
  KEY `cookieId` (`cookieId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE IF NOT EXISTS `product_xref` (
  `id` int(11) NOT NULL auto_increment,
  `product_id_1` int(11) default NULL,
  `product_id_2` int(11) default NULL,
  `hits` int(11) NOT NULL default '0',
  PRIMARY KEY  (`id`),
  KEY `IDX_PROD1` (`product_id_1`),
  KEY `IDX_PROD2` (`product_id_2`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=184531 ;

CREATE TABLE IF NOT EXISTS `product_product` (
  `id` int(11) NOT NULL auto_increment,
  `supplierid` int(11) NOT NULL default '0',
  `suppliersku` varchar(100) NOT NULL default '',
  `name` varchar(100) NOT NULL default '',
  `cost` decimal(10,2) NOT NULL default '0.00',
  `retailprice` decimal(10,2) NOT NULL default '0.00',
  `weight` decimal(10,2) NOT NULL default '0.00',
  `imageurl` varchar(255) NOT NULL default '',
  `thumbnailurl` varchar(255) NOT NULL default '',
  `sizechartlink` varchar(255) NOT NULL default '',
  `content` text NOT NULL,
  `remark` varchar(100) NOT NULL default '',
  `colorchartlink` varchar(255) default NULL,
  `outofstock` char(1) NOT NULL default '',
  `summary` text NOT NULL,
  `freehandoutlink` varchar(255) default NULL,
  `msrp` decimal(10,2) default NULL,
  `enabled` tinyint(1) NOT NULL default '1',
  `sales_score` float NOT NULL default '0',
  `sales_score_offset` float NOT NULL default '0',
  `date_added` timestamp NULL default CURRENT_TIMESTAMP,
  `brand` varchar(255) default NULL,
  `tag_status` varchar(20) default NULL,
  PRIMARY KEY  (`id`),
  KEY `product_retailprice_idx` (`retailprice`),
  KEY `suppliersku` (`suppliersku`),
  FULLTEXT KEY `product_name_summary_ft` (`name`,`summary`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

另外,根据请求,EXPLAIN的结果:

+----+-------------+---------------------+------+---------------------+----------+---------+-------+-------+------------------------------------------------+
| id | select_type | table               | type | possible_keys       | key      | key_len | ref   | rows  | Extra                                          |
+----+-------------+---------------------+------+---------------------+----------+---------+-------+-------+------------------------------------------------+
|  1 | SIMPLE      | product_xref        | ALL  | IDX_PROD1,IDX_PROD2 | NULL     | NULL    | NULL  | 30035 | Using temporary; Using filesort                |
|  1 | SIMPLE      | product_viewhistory | ref  | cookieId            | cookieId | 92      | const |   682 | Using where                                    |
|  1 | SIMPLE      | product_product     | ALL  | PRIMARY             | NULL     | NULL    | NULL  | 31880 | Range checked for each record (index map: 0x1) |
+----+-------------+---------------------+------+---------------------+----------+---------+-------+-------+------------------------------------------------+
3 rows in set (0.00 sec)

新的更新版本,因为我意识到我根本不需要product_viewhistory。我被旧代码留下了:

SELECT DISTINCT product_product.id, product_product.name, product_product.retailprice, product_product.imageurl, product_product.thumbnailurl, product_product.msrp
FROM product_product, product_xref
WHERE 
(
(product_xref.product_id_1 IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172) AND product_xref.product_id_2 = product_product.id)
OR 
(product_xref.product_id_2 IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172) AND product_xref.product_id_1 = product_product.id)
)
AND product_product.outofstock='N'
ORDER BY product_xref.hits DESC
LIMIT 10

新解释:

+----+-------------+-----------------+-------------+---------------------+---------------------+---------+------+-------+-------------------------------------------------------------------------------------+
| id | select_type | table           | type        | possible_keys       | key                 | key_len | ref  | rows  | Extra                                                                               |
+----+-------------+-----------------+-------------+---------------------+---------------------+---------+------+-------+-------------------------------------------------------------------------------------+
|  1 | SIMPLE      | product_xref    | index_merge | IDX_PROD1,IDX_PROD2 | IDX_PROD1,IDX_PROD2 | 5,5     | NULL |    32 | Using sort_union(IDX_PROD1,IDX_PROD2); Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | product_product | ALL         | PRIMARY             | NULL                | NULL    | NULL | 31880 | Range checked for each record (index map: 0x1)                                      |
+----+-------------+-----------------+-------------+---------------------+---------------------+---------+------+-------+-------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

3 个答案:

答案 0 :(得分:1)

要做的第一件事就是看看MySQL在EXPLAIN下做了什么,然后从那里开始。听起来你有一些索引要做。

答案 1 :(得分:1)

我将您的查询重写为:

   SELECT DISTINCT
          pp.id, 
          pp.name, 
          pp.retailprice, 
          pp.imageurl, 
          pp.thumbnailurl,
          pp.msrp
     FROM PRODUCT_PRODUCT pp
LEFT JOIN PRODUCT_XREF px1 ON px1.product_id_2 = pp.id
LEFT JOIN PRODUCT_XREF px2 ON px2.product_id_1 = pp.id
    WHERE EXISTS(SELECT NULL
                   FROM PRODUCT_VIEWHISTORY pvh
                  WHERE pvh.productid = px1.product_id_1
                    AND pvh.cookieId = '188af1efad392c2adf82'
                    AND pvh.productId IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172))
       OR EXISTS(SELECT NULL
                   FROM PRODUCT_VIEWHISTORY pvh
                  WHERE pvh.productid = px2.product_id_2
                    AND pvh.cookieId = '188af1efad392c2adf82'
                    AND pvh.productId IN (24976, 25873, 26067, 26073, 44949, 16209, 70528, 69784, 75171, 75172))
      AND pp.outofstock = 'N'
 ORDER BY GREATEST(px1.hits, px2.hits) DESC
    LIMIT 10

如果ORDER BY不依赖于PRODUCT_XREF.hits列,那将会更容易。太糟糕的MySQL不支持公用表表达式(CTE)/子查询因子...

有两个不同的product_id引用是一个非常值得怀疑的方法。我建议查看数据模型。

答案 2 :(得分:0)

您需要优化查询。使用EXPLAIN从mysql提示符或mysql客户端运行它并检查执行计划。您可能需要向表中添加索引。请记住,如果您运行此查询几次 连续,mysql服务器将缓存结果,你不应该依赖他们的快速执行时间。也许这就是为什么你的查询在90%的时间内运行良好的原因。