大型INNODB桌子锁定

时间:2013-08-28 13:50:43

标签: mysql sql innodb

我有一个数据库表让我头疼,插入大量数据时出错。让我分解一下究竟发生了什么,我希望有人能够深入了解如何解决这个问题。

基本上我有一张表,其中有超过1100万条记录,而且它每天都在增长。我们会跟踪用户观看视频的时间以及他们在该视频中的进度。你可以在下面看到结构是什么样的。我们的设置是一个主数据库,附有两个从属设备。每晚我们运行一个cron脚本来编译这个表中的一些统计数据,并将它们编译成我们仅用于报告的其他几个表。这些cron脚本只在slave上执行SELECT语句,并将插入到master上的统计表中(因此它将向下传播)。就像发条一样,每次我们运行这个脚本时,它都会锁定我们的生产表。我认为将SELECT移动到slave会解决这个问题,因为我们甚至没有用cron而不是其他表写入主表,我现在感到困惑的是可能导致这种锁定。

几乎好像每次主表(主站或从站)上的大读取都会锁定主站。一旦cron完成,表就会恢复正常性能。

我的问题是关于INNODB的几个层面。我有一些想法,它可能会导致这个问题的索引,但也许它是INNODB设置上的其他变量,我还没有完全理解。正如你想象的那样,我想让主人不要得到这个锁定。我不关心在这个脚本运行期间是否挂起了奴隶,只要它不会影响我的主数据库。这是MYSQL中Slave / Master关系可能发生的事情吗?

获取编译信息的表格为stats_daily,stats_grouped供参考。

我在这里提出的最大问题,重申一点,就是我不明白会导致锁定的原因。从主数据库读取并只是插入到另一个表中似乎不应该在主原始表上执行任何操作。我可以看到错误开始流入,但是,在脚本启动后3分钟,它将在脚本停止时立即结束。

我正在使用的表格如下。

CREATE TABLE IF NOT EXISTS `stats` (
  `ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `VID` int(10) unsigned NOT NULL DEFAULT '0',
  `UID` int(10) NOT NULL DEFAULT '0',
  `Position` smallint(10) unsigned NOT NULL DEFAULT '0',
  `Progress` decimal(3,2) NOT NULL DEFAULT '0.00',
  `ViewCount` int(10) unsigned NOT NULL DEFAULT '0',
  `DateFirstView` int(10) unsigned NOT NULL DEFAULT '0', // Use unixtimestamps
  `DateLastView` int(10) unsigned NOT NULL DEFAULT '0', // Use unixtimestamps
  PRIMARY KEY (`ID`),
  KEY `VID` (`VID`,`UID`),
  KEY `UID` (`UID`),
  KEY `DateLastView` (`DateLastView`),
  KEY `ViewCount` (`ViewCount`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=15004624 ;

有没有人对此有任何想法或想法?

更新 我从主DB获得的错误

MysqlError: Lock wait timeout exceeded; try restarting transaction

Uncaught exception 'Exception' with message 'invalid query UPDATE stats SET VID = '13156', UID = '73859', Position = '0', Progress = '0.8', ViewCount = '1', DateFirstView = '1375789950', DateLastView = '1375790530' WHERE ID = 14752456

由于锁定,更新查询失败。该查询实际上是有效的。我将获得100个这些,然后我可以随机复制/粘贴这些查询,它们将起作用。

更新2 来自Cron脚本的查询和解释

在Slave上查询Ran(将php变量留在大括号中以供参考):

SELECT 
    VID, 
    COUNT(ID) as ViewCount,
    DATE_FORMAT(FROM_UNIXTIME(DateLastView), '%Y-%m-%d') AS YearMonthDay,
    {$today} as DateModified
FROM stats
WHERE DateLastView >= {$start_date} AND DateLastView <= {$end_date}
GROUP BY YearMonthDay, VID 

SELECT Stat的解析

id  select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   SIMPLE          stats   range   DateLastView    DateLastView    4   NULL    25242   Using where; Using temporary; Using filesort

该结果集循环并插入到已编译的表中。不幸的是我没有支持批量插入这个(我试过)所以我必须一次循环这些,而不是一次发送一批100或500到服务器。这将插入到主DB中。

foreach ($results as $result)
{
    $query = "INSERT INTO stats_daily (VID, ViewCount, YearMonthDay, DateModified) VALUES ({$result->VID}, {$result->ViewCount}, '{$result->YearMonthDay}', {$today} );

    DoQuery($query);
}

1 个答案:

答案 0 :(得分:0)

GROUP BY是罪魁祸首。显然MySQL决定在这种情况下使用临时表(可能是因为表超出了某些限制),这是非常低效的。

我遇到了类似的问题,但没有明确的解决方案。您可以考虑将stats表分成两个表,每天一次&#39;和历史&#39;表。在&#39;每日&#39;上运行您的查询表格只包含最近24小时内的条目或您的间隔时间,然后清理表格。 将信息输入您的永久历史记录中table,要么将代码写入代码中的两个表,要么在清理之前将它们从每天复制到历史记录中。