2013-08-28 60 views
1

我有一个数据库表,给我插入大量数据时的头痛,错误。让我分解究竟发生了什么,我希望有人对我如何解决这个问题有一些了解。大INNODB表锁定

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

这似乎似乎每次在主表(主或从)上的大量读取都会锁定主设备。一旦cron完成,表格就会恢复正常性能。

我的问题是关于INNODB的几个级别。我有这样的想法,它可能是索引,会导致这个问题,但也许它是INNODB设置的其他变量,我不完全理解。正如你想象的那样,我想让主人避免这个锁定。我并不关心在这个脚本运行过程中是否挂钩了slave,只要它不会影响我的master db。这在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 ; 

有没有人有任何想法或想法呢?

UPDATE: 的错误,我从主数据库

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个,然后我可以随机复制/粘贴这些查询,他们将工作。

UPDATE 2 查询和从克朗脚本解释

查询然在从站(在大括号离开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统计的

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。这被插入主数据库。

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

    DoQuery($query); 
} 
+0

如果从主设备上看到任何错误,而在设备处理过程中设备停止在从设备上。 – RiggsFolly

+0

我认为你也应该发送你的cronjob脚本的部分内容来查询你运行的是什么查询,并在这些查询上运行EXPLAIN,并在这里发布结果,并提供关于什么查询生成了什么解释的很好的信息。 –

+0

检查你的mysql tmp dir(如果你在linux上可能是/ tmp,看看是否有MYD或MYI文件,如果你运行cronjob make,请使用ll并输入几次关闭时间)。如果因为基于磁盘的临时表被创建而不好,那么会导致性能下降 –

回答

0

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

我碰到类似的问题,但没有明确的解决方案。您可以考虑将您的stats表分成两个表格,一个“每日”和一个“历史”表格。在“每日”表上运行您的查询,该表仅包含最近24小时内的条目或任何时间间隔,然后清理表格。 要将信息存入永久的“历史记录”表中,请将代码中的统计信息写入两个表中,或者在清理前将其从日常记录复制到历史记录中。