1

我有一个数据库表,在插入大量数据时让我很头疼、出错。让我分解到底发生了什么,我希望有人能对我如何弄清楚这一点有所了解。

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

似乎每次对主表(主表或从表)进行大量读取时,它都会锁定主表。一旦 cron 完成,表就会恢复正常性能。

我的问题是关于 INNODB 的几个级别。我曾想过可能是索引会导致这个问题,但也许是我不完全理解的 INNODB 设置上的其他变量。正如你想象的那样,我想阻止主人得到这个锁定。只要它不会影响我的主数据库,我真的不在乎在此脚本运行期间是否将奴隶挂出。MYSQL 中的从/主关系会发生这种情况吗?

获取编译信息的表是 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 ;

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

更新: 我从主数据库得到的错误

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

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

    DoQuery($query);
}
4

1 回答 1

0

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

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

于 2015-12-07T15:19:49.527 回答