Php 处理数百万行时,PDO DELETE异常缓慢

Php 处理数百万行时,PDO DELETE异常缓慢,php,mysql,pdo,Php,Mysql,Pdo,我正在使用一个大约有1200万行的MYISAM表。方法用于删除早于指定日期的所有记录。该表在日期字段上建立索引。当在代码中运行时,日志显示,当没有要删除的记录时,这大约需要13秒,当有1天的记录时,这大约需要25秒。当在mysql客户机中运行相同的查询时(在代码运行时从ShowProcessList中获取查询),没有记录根本不需要时间,一天的记录大约需要16秒 现实生活中的问题是,每天运行一次时,如果有记录要删除,则需要很长时间,因此更频繁地运行它似乎是合乎逻辑的。但我希望它在无事可做时尽快退出

我正在使用一个大约有1200万行的MYISAM表。方法用于删除早于指定日期的所有记录。该表在日期字段上建立索引。当在代码中运行时,日志显示,当没有要删除的记录时,这大约需要13秒,当有1天的记录时,这大约需要25秒。当在mysql客户机中运行相同的查询时(在代码运行时从ShowProcessList中获取查询),没有记录根本不需要时间,一天的记录大约需要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
    }
删除0行时的mysql客户端:

    [debug] ScriptController::actionDeleteHistory() success in 12.82 seconds
    mysql> DELETE FROM user_history WHERE dateSent < '2013-05-03 13:41:55';
    Query OK, 0 rows affected (0.00 sec)
    [debug] ScriptController::actionDeleteHistory() success in 25.48 seconds
    mysql> DELETE FROM user_history WHERE dateSent < '2013-05-05 13:41:55';
    Query OK, 672260 rows affected (15.70 sec)
mysql客户端1天删除结果时:

    [debug] ScriptController::actionDeleteHistory() success in 12.82 seconds
    mysql> DELETE FROM user_history WHERE dateSent < '2013-05-03 13:41:55';
    Query OK, 0 rows affected (0.00 sec)
    [debug] ScriptController::actionDeleteHistory() success in 25.48 seconds
    mysql> DELETE FROM user_history WHERE dateSent < '2013-05-05 13:41:55';
    Query OK, 672260 rows affected (15.70 sec)
此时,表模式可能会受到质疑,因此:

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;
这是一个相当大的网站,有很多数据库调用。我看不到任何证据表明,该网站在其他方面的表现都是不可靠的。特别是当我在ShowProcessList上看到这个查询在PHP/PDO中运行时缓慢地爬行到13秒,但在mysql中运行时根本不需要时间(特别是指不删除任何记录的情况,仅在PHP/PDO中需要13秒)

目前只有这个特定的删除查询有问题。但是在这个项目中,或者我能想到的任何其他项目中,我都没有像这样的批量删除语句。因此,对于大型ish表上的PDO DELETE查询,问题是特别的

“那么这不是你的答案吗?”-不是。问题是,为什么与mysql客户端相比,PHP/PDO需要更长的时间。ShowProcessList仅显示该查询在PHP/PDO中所花费的时间(对于不需要删除的记录)。在mysql客户机中根本不需要时间。这就是重点

DROP DATABASE IF EXISTS pdo_test;
CREATE DATABASE IF NOT EXISTS pdo_test;
USE pdo_test;

CREATE TABLE IF NOT EXISTS test (
  `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;

drop procedure if exists load_test_data;

delimiter #
create procedure load_test_data()
    begin
        declare v_max int unsigned default 10000000;
        declare v_counter int unsigned default 0;

        while v_counter < v_max do
            INSERT INTO test (userId, asin, dateSent) VALUES (FLOOR(1 + RAND()*10000000), SUBSTRING(MD5(RAND()) FROM 1 FOR 10), NOW());
            set v_counter=v_counter+1;
        end while;
    end #
delimiter ;

ALTER TABLE test DISABLE KEYS;
call load_test_data();
ALTER TABLE test ENABLE KEYS;

# Tests - reconnect to mysql client after each one to reset previous CHARACTER SET

# Right collation, wrong charset - slow
SET CHARACTER SET utf8;
ALTER DATABASE pdo_test COLLATE='utf8_unicode_ci';
DELETE FROM test  WHERE dateSent < '2013-01-01 00:00:00';

# Wrong collation, no charset - fast
ALTER DATABASE pdo_test COLLATE='latin1_swedish_ci';
DELETE FROM test  WHERE dateSent < '2013-01-01 00:00:00';

# Right collation, right charset - fast
SET NAMES utf8;
ALTER DATABASE pdo_test COLLATE='utf8_unicode_ci';
DELETE FROM test  WHERE dateSent < '2013-01-01 00:00:00';
在没有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);

将PDO/ATTR_仿真_添加到DB::connect()。在删除任何记录时仍有延迟。我以前没有使用过这个,但它看起来是正确的格式:

   $this->dbh->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);

当前的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);
 }

索引显示在上面的模式中。如果它与删除记录后重建索引直接相关,那么mysql将花费与PHP/PDO相同的时间。没有。这就是问题所在。这并不是说这个查询很慢,而是需要一些时间。这是因为PHP/PDO明显比在mysql客户机中执行的查询或在PHP中使用mysql库的查询慢


MYSQL\u ATTR\u已尝试使用\u缓冲\u查询,但仍有延迟


DB是标准的单例模式。DB::getInstance()->getDbh()返回在上面所示的DB::connect()方法中创建的PDO连接对象,例如:DB::dbh。我相信我已经证明了DB singleton不是一个问题,因为在使用与执行查询相同的方法创建PDO连接时仍然存在延迟(上面的6个编辑)


我已经找到了原因,但我不知道为什么这一刻会发生

我创建了一个测试SQL,它以正确的格式创建了一个包含1000万个随机行的表,并创建了一个PHP脚本来运行有问题的查询。而且在PHP/PDO或mysql客户端中根本不需要时间。然后我将DB排序规则从默认的latin1_swedish_ci更改为utf8_unicode_ci,在PHP/PDO中需要10秒,在mysql客户端中根本不需要时间。然后我把它改回latin1_swedish_ci,在PHP/PDO中再也不用花时间了

塔达

现在,如果我从DB连接中删除它,它在两种排序规则中都可以正常工作。所以这里有一些问题:

 $dbh->exec("SET CHARACTER SET utf8");

我将进行更多研究,然后稍后跟进。

尝试分析和优化表格:

所以

这篇文章解释了缺陷所在

基本上,这是使用:

$this->dbh->exec("SET CHARACTER SET utf8");
在DB::connect()中应该是这样的

完全是我的错

这似乎产生了可怕的影响,因为mysql服务器需要转换查询以匹配数据库的排序规则。上面的帖子比我能提供的详细得多

如果有人需要确认我的发现,这一系列SQL查询将设置一个测试数据库,并允许您自己进行检查。只需确保在输入测试数据后正确启用索引,因为出于某种原因,我不得不删除并重新添加这些索引。它创建了1000万行。也许少一点就足以证明这一点

DROP DATABASE IF EXISTS pdo_test;
CREATE DATABASE IF NOT EXISTS pdo_test;
USE pdo_test;

CREATE TABLE IF NOT EXISTS test (
  `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;

drop procedure if exists load_test_data;

delimiter #
create procedure load_test_data()
    begin
        declare v_max int unsigned default 10000000;
        declare v_counter int unsigned default 0;

        while v_counter < v_max do
            INSERT INTO test (userId, asin, dateSent) VALUES (FLOOR(1 + RAND()*10000000), SUBSTRING(MD5(RAND()) FROM 1 FOR 10), NOW());
            set v_counter=v_counter+1;
        end while;
    end #
delimiter ;

ALTER TABLE test DISABLE KEYS;
call load_test_data();
ALTER TABLE test ENABLE KEYS;

# Tests - reconnect to mysql client after each one to reset previous CHARACTER SET

# Right collation, wrong charset - slow
SET CHARACTER SET utf8;
ALTER DATABASE pdo_test COLLATE='utf8_unicode_ci';
DELETE FROM test  WHERE dateSent < '2013-01-01 00:00:00';

# Wrong collation, no charset - fast
ALTER DATABASE pdo_test COLLATE='latin1_swedish_ci';
DELETE FROM test  WHERE dateSent < '2013-01-01 00:00:00';

# Right collation, right charset - fast
SET NAMES utf8;
ALTER DATABASE pdo_test COLLATE='utf8_unicode_ci';
DELETE FROM test  WHERE dateSent < '2013-01-01 00:00:00';
如果存在pdo_测试,则删除数据库;
如果不存在pdo_测试,则创建数据库;
使用pdo_测试;
如果不存在测试,则创建表(
`userId`int(11)不为空,
`asin`varchar(10)校对utf8\u unicode\u ci不为空,
`dateSent`时间戳不为空默认当前时间戳,
主键(`userId`、`asin`),
键'date\u sent'('dateSent`)
)ENGINE=MyISAM默认字符集=utf8 COLLATE=utf8\U unicode\U ci;
如果存在负载测试数据,则丢弃程序;
分隔符#
创建过程加载测试数据()
开始
声明v_max int未签名默认值10000000;
声明v_计数器int无符号默认值为0;
当v_计数器 $dbh->exec("SET CHARACTER SET utf8");
$this->dbh->exec("SET CHARACTER SET utf8");
$this->dbh->exec("SET NAMES utf8");
DROP DATABASE IF EXISTS pdo_test;
CREATE DATABASE IF NOT EXISTS pdo_test;
USE pdo_test;

CREATE TABLE IF NOT EXISTS test (
  `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;

drop procedure if exists load_test_data;

delimiter #
create procedure load_test_data()
    begin
        declare v_max int unsigned default 10000000;
        declare v_counter int unsigned default 0;

        while v_counter < v_max do
            INSERT INTO test (userId, asin, dateSent) VALUES (FLOOR(1 + RAND()*10000000), SUBSTRING(MD5(RAND()) FROM 1 FOR 10), NOW());
            set v_counter=v_counter+1;
        end while;
    end #
delimiter ;

ALTER TABLE test DISABLE KEYS;
call load_test_data();
ALTER TABLE test ENABLE KEYS;

# Tests - reconnect to mysql client after each one to reset previous CHARACTER SET

# Right collation, wrong charset - slow
SET CHARACTER SET utf8;
ALTER DATABASE pdo_test COLLATE='utf8_unicode_ci';
DELETE FROM test  WHERE dateSent < '2013-01-01 00:00:00';

# Wrong collation, no charset - fast
ALTER DATABASE pdo_test COLLATE='latin1_swedish_ci';
DELETE FROM test  WHERE dateSent < '2013-01-01 00:00:00';

# Right collation, right charset - fast
SET NAMES utf8;
ALTER DATABASE pdo_test COLLATE='utf8_unicode_ci';
DELETE FROM test  WHERE dateSent < '2013-01-01 00:00:00';