我正在使用一个大约有1200万行的MYISAM表.方法用于删除早于指定日期的所有记录.该表在日期字段上编制索引.在代码中运行时,日志显示当没有要删除的记录时这需要大约13秒,而当有1天的记录时大约需要25秒.当在mysql客户端中运行相同的查询时(在代码运行时从SHOW PROCESSLIST获取查询),它根本没有时间没有记录,一天的记录大约需要16秒.
现实生活中的问题是,当每天运行一次有删除记录时需要花费很长时间,因此更频繁地运行它似乎是合乎逻辑的.但是当我无所事事时,我希望它能尽快退出.
方法提取:
try {
$smt = DB::getInstance()->getDbh()->prepare("DELETE FROM " . static::$table . " WHERE dateSent < :date");
$smt->execute(array(':date' => $date));
return true;
} catch (\PDOException $e) {
// Some logging here removed to ensure a clean test
}
Run Code Online (Sandbox Code Playgroud)
删除0行时记录结果:
[debug] ScriptController::actionDeleteHistory() success in 12.82 seconds
Run Code Online (Sandbox Code Playgroud)
mysql客户端当0行删除时:
mysql> DELETE FROM user_history WHERE dateSent < '2013-05-03 13:41:55';
Query OK, 0 rows affected (0.00 sec)
Run Code Online (Sandbox Code Playgroud)
1天结果删除时记录结果:
[debug] ScriptController::actionDeleteHistory() success in 25.48 seconds
Run Code Online (Sandbox Code Playgroud)
mysql客户端1天后删除结果:
mysql> DELETE FROM user_history WHERE dateSent < '2013-05-05 13:41:55';
Query OK, 672260 rows affected (15.70 sec)
Run Code Online (Sandbox Code Playgroud)
PDO速度慢的原因是什么?
干杯.
回复评论:
两者都是相同的查询,因此索引要么被拾取,要么不被接收.它是.
EXPLAIN SELECT * FROM user_history WHERE dateSent < '2013-05-05 13:41:55'
1 SIMPLE user_history range date_sent date_sent 4 NULL 4 Using where
Run Code Online (Sandbox Code Playgroud)
出于此测试的目的,MySQL和Apache在同一服务器上运行.如果你遇到了一个加载问题,那么mysql在代码内查询的13秒内确实达到了100%.在mysql客户端查询中,它在查询完成之前没有机会在顶部注册.我看不出PHP/PDO是如何添加到等式中的,但我对所有想法持开放态度.
:date是PDO占位符,fieldname是dateSent,因此不会与mysql关键字冲突.仍然,使用:dateSent仍然会导致延迟.
也已经尝试过不使用占位符但忽略了提到这么好的电话,谢谢!顺着这个.PHP/PDO的延迟仍然相同.
DB::getInstance()->getDbh()->query(DELETE FROM user_history WHERE dateSent < '2013-05-03 13:41:55')
Run Code Online (Sandbox Code Playgroud)
在mysql客户端使用占位符仍然没有显示延迟:
PREPARE test from 'DELETE FROM user_history WHERE dateSent < ?';
SET @datesent='2013-05-05 13:41:55';
EXECUTE test USING @datesent;
Query OK, 0 rows affected (0.00 sec)
Run Code Online (Sandbox Code Playgroud)
这是一张MYISAM表,因此没有涉及此交易.
$ date的值不同于测试没有删除或一天的删除,如在mysql客户端上运行的查询中所示,该代码运行时从SHOW PROCESSLIST获取.在这种情况下,它不会传递给方法,而是派生自:
if (!isset($date)) {
$date = date("Y-m-d H:i:s", strtotime(sprintf("-%d days", self::DELETE_BEFORE)));
}
Run Code Online (Sandbox Code Playgroud)
此时,表模式可能会受到质疑,因此:
CREATE TABLE IF NOT EXISTS `user_history` (
`userId` int(11) NOT NULL,
`asin` varchar(10) COLLATE utf8_unicode_ci NOT NULL,
`dateSent` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`userId`,`asin`),
KEY `date_sent` (`dateSent`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Run Code Online (Sandbox Code Playgroud)
它是一个体面的网站,有很多数据库调用.我认为该网站在任何其他方面的表现方式都没有任何证据表明它可以归结为狡猾的路由.特别是当我在SHOW PROCESSLIST上看到这个查询时,在PHP/PDO中运行时慢慢爬上13秒,但是在mysql中运行时根本不需要时间(特别是指没有记录需要删除的时间需要13秒)仅限PHP/PDO).
目前只有这个特定的DELETE查询才有问题.但是我在这个项目的其他任何地方都没有这样的批量DELETE语句,或者我能想到的任何其他项目.因此,问题特别针对大型表上的PDO DELETE查询.
"那不是你的答案吗?" - 不.问题是为什么与mysql客户端相比,这在PHP/PDO中需要更长的时间.SHOW PROCESSLIST仅显示此查询在PHP/PDO中花费时间(不删除任何记录).它在mysql客户端中根本没有时间.这才是重点.
尝试没有try-catch块的PDO查询,仍然存在延迟.
尝试使用mysql_*函数显示与直接使用mysql客户端相同的时序.因此,手指现在非常强烈地指向PDO.它可能是我的代码与PDO接口,但由于没有其他查询有意外的延迟,这似乎不太可能:
方法:
$conn = mysql_connect(****);
mysql_select_db(****);
$query = "DELETE FROM " . static::$table . " WHERE dateSent < '$date'";
$result = mysql_query($query);
Run Code Online (Sandbox Code Playgroud)
记录没有要删除的记录:
Fri May 17 15:12:54 [verbose] UserHistory::deleteBefore() query: DELETE FROM user_history WHERE dateSent < '2013-05-03 15:12:54'
Fri May 17 15:12:54 [verbose] UserHistory::deleteBefore() result: 1
Fri May 17 15:12:54 [verbose] ScriptController::actionDeleteHistory() success in 0.01 seconds
Run Code Online (Sandbox Code Playgroud)
记录要删除的一天记录:
Fri May 17 15:14:24 [verbose] UserHistory::deleteBefore() query: DELETE FROM user_history WHERE dateSent < '2013-05-07 15:14:08'
Fri May 17 15:14:24 [verbose] UserHistory::deleteBefore() result: 1
Fri May 17 15:14:24 [debug] ScriptController::apiReturn(): {"message":true}
Fri May 17 15:14:24 [verbose] ScriptController::actionDeleteHistory() success in 15.55 seconds
Run Code Online (Sandbox Code Playgroud)
并再次尝试通过在方法中创建PDO连接并使用它来避免对DB单例的调用,这又有一个延迟.虽然其他查询都没有其他延迟,所有使用相同的DB单例都值得一试,但并没有真正期望有任何区别:
$connectString = sprintf('mysql:host=%s;dbname=%s', '****', '****');
$dbh = new \PDO($connectString, '****', '****');
$dbh->exec("SET CHARACTER SET utf8");
$dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$smt = $dbh->prepare("DELETE FROM " . static::$table . " WHERE dateSent < :date");
$smt->execute(array(':date' => $date));
Run Code Online (Sandbox Code Playgroud)
使用时间记录器调用方法:
$startTimer = microtime(true);
$deleted = $this->apiReturn(array('message' => UserHistory::deleteBefore()));
$timeEnd = microtime(true) - $startTimer;
Logger::write(LOG_VERBOSE, "ScriptController::actionDeleteHistory() success in " . number_format($timeEnd, 2) . " seconds");
Run Code Online (Sandbox Code Playgroud)
将PDO/ATTR_EMULATE_PREPARES添加到DB :: connect().根本没有删除记录时仍有延迟.我以前没用过这个,但它看起来像是正确的格式:
$this->dbh->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
Run Code Online (Sandbox Code Playgroud)
当前的DB :: connect()虽然如果存在这方面的一般问题,肯定会影响所有查询?
public function connect($host, $user, $pass, $name)
{
$connectString = sprintf('mysql:host=%s;dbname=%s', $host, $name);
$this->dbh = new \PDO($connectString, $user, $pass);
$this->dbh->exec("SET CHARACTER SET utf8");
$this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
Run Code Online (Sandbox Code Playgroud)
索引显示在架构的上方.如果它与删除记录后重建索引直接相关,那么mysql将花费与PHP/PDO相同的时间.它没有.这是问题所在.并不是说这个查询很慢 - 预计需要一些时间.这是PHP/PDO明显慢于在mysql客户端中执行的查询或在PHP中使用mysql lib的查询.
尝试了MYSQL_ATTR_USE_BUFFERED_QUERY,但仍有延迟
DB是标准的单例模式.DB :: getInstance() - > getDbh()返回在上面显示的DB :: connect()方法中创建的PDO连接对象,例如:DB :: dbh.我相信我已经证明了DB单例不是问题,因为在执行查询的同一方法中创建PDO连接时仍有延迟(上面的6个编辑).
我发现了它造成了什么,但我不知道为什么这一刻正好发生.
我创建了一个测试SQL,它创建了一个包含1000万个正确格式的随机行的表,以及一个运行违规查询的PHP脚本.在PHP/PDO或mysql客户端中它根本不需要时间.然后我将DB排序规则从默认的latin1_swedish_ci更改为utf8_unicode_ci,在PHP/PDO中需要10秒,而在mysql客户端中根本没有时间.然后我将它改回latin1_swedish_ci,它再次在PHP/PDO中没有时间.
田田!
现在,如果我从数据库连接中删除它,它在任何排序规则中都可以正常工作.所以这里有一些问题:
$dbh->exec("SET CHARACTER SET utf8");
Run Code Online (Sandbox Code Playgroud)
我会研究更多,然后再跟进.