Mysql 搜索大量行

Mysql search on high number of rows

我尝试在 mysql 数据库中导入相对较多的数据(大约 600 万条条目来自文本文件)。

如果数据库中还没有类似的记录,我必须通过将其与两个文本字段进行比较来检查每个条目:

`ref` varchar(30) COLLATE utf8_unicode_ci NOT NULL
`labelCanonical` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL

文件按 N 个条目的批次处理(对于本例为 10 个条目),我执行单个查询来检查批次中的所有重复项,如下所示:

SELECT p.`ref`, p.`labelCanonical` 
FROM `rtd_piece` p 
WHERE (p.`ref` = "6569GX" AND p.`labelCanonical` = "fsc-principal") 
  OR (p.`ref` = "6569GY" AND p.`labelCanonical` = "fsc-principal") 
  OR (p.`ref` = "6569GZ" AND p.`labelCanonical` = "fsc-principal") 
  OR (p.`ref` = "6569H0" AND p.`labelCanonical` = "fsc-habitacle") 
  OR (p.`ref` = "6569H1" AND p.`labelCanonical` = "support-fsc") 
  OR (p.`ref` = "6569H2" AND p.`labelCanonical` = "fsc-injection") 
  OR (p.`ref` = "6569H4" AND p.`labelCanonical` = "fsc-injection") 
  OR (p.`ref` = "6569H8" AND p.`labelCanonical` = "faisceau-mot") 
  OR (p.`ref` = "6569H9" AND p.`labelCanonical` = "faisceau-mot") 
  OR (p.`ref` = "6569HA" AND p.`labelCanonical` = "fsc-principal")

我使用 Doctrine 2(没有 Symfony),并且我使用 "NativeQuery".

执行此查询

这个问题是,即使数据库中有 600k 个条目,此查询也需要 730 毫秒 (或 6.7 秒,一批 100 条记录) 执行并随着记录添加到数据库中而急剧增加。

我目前在 "ref" 或 "labelCanonical" 字段上没有索引,我不确定添加一个索引是否会对我的请求有任何好处。

这个方法我哪里错了,所以它这么慢?

编辑以添加有关进程的更多信息。

我为每个批次做一个ajax查询,也是为了给用户一个反馈。 在服务器端 (PHP) 时,我执行以下过程:

1) 我在处理中查找当前文件并提取下N条记录

2) 我解析每一行并将引用和 slugified 标签添加到两个不同的数组

3) 我尝试从数据库中获取这些记录以避免重复:

$existing = array();
$results = getRepository('Piece')->findExistingPieces($refs, $labels);
for ($i = 0, $c = count($results); $i < $c; ++$i) {
    $existing[] = $results[$i]['ref'].'|'.$results[$i]['labelCanonical'];
}
public function findExistingPieces(array $refs, array $labels)
{
    $sql = '';
    $where = array();
    $params = array();

    for ($i = 0, $c = count($refs); $i < $c; ++$i) {
        $params[] = $refs[$i];
        $params[] = $labels[$i];
        $where[] = '(p.`ref` = ? AND p.`labelCanonical` = ?)';
    }

    $sql = 'SELECT p.`ref`, p.`labelCanonical` '.
           'FROM `rtd_piece` p '.
           'WHERE '.implode(' OR ', $where);

    $rsm = new ResultSetMapping;
    $rsm->addScalarResult('ref', 'ref');
    $rsm->addScalarResult('labelCanonical', 'labelCanonical');

    $query = $this->getEntityManager()
                  ->createNativeQuery($sql, $rsm)
                  ->setParameters($params);
    return $query->getScalarResult();
}

4) 我遍历以前解析的数据并检查重复项:

for ($i = 0; $i < $nbParsed; ++$i) {
    $data = $parsed[$i];

    if (in_array($data['ref'].'|'.$data['labelCanonical'], $existing)) {
        // ...
        continue ;
    }
    // Add record
    $piece = new PieceEntity;
    $piece->setRef($data['ref']);
    //...

    $em->persist($piece);
}

5) 我在批次结束时冲洗

我添加了一些 "profiling" 代码来跟踪每个步骤所花费的时间,这里是结果:

0.00024509429931641 (0.245 ms) : Initialized
0.00028896331787109 (0.289 ms) : Start doProcess
0.00033092498779297 (0.331 ms) : Read and parse lines
0.0054769515991211 (5.477 ms) : Check existence in database
6.9432899951935 (6,943.290 ms) : Process parsed data
6.9459540843964 (6,945.954 ms) : Finilize
6.9461529254913 (6,946.153 ms) : End of process
6.9464020729065 (6,946.402 ms) : End doProcess
6.9464418888092 (6,946.442 ms) : Return result

第一个数字显示自请求开始以来经过的微秒数,然后是同一时间(以毫秒为单位),然后是正在执行的操作。

首先,我建议您使用行构造函数编写此代码:

SELECT p.`ref`, p.`labelCanonical` 
FROM `rtd_piece` p 
WHERE (p.`ref`, p.`labelCanonical`) IN  ( ('6569GX', 'fsc-principal'),
                                          ('6569GY', 'fsc-principal'),
                                          . . .
                                        );

这不会影响性能,但更易于阅读。然后,您需要一个索引,rtd_piece(ref, labelCanonical)rtd_piece(labelCanonical, ref).

所以经过一些重构,这就是我带来的:

我使用名为 "hash" 的新字段检查重复项,如下所示:

$existing = array();
$results = getRepository('Piece')->findExistingPiecesByHashes($hashes);
for ($i = 0, $c = count($results); $i < $c; ++$i) {
    $existing[] = $results[$i]['hash'];
}
public function findExistingPiecesByHashes(array $hashes)
{
    $sql = 'SELECT p.`ref`, p.`labelCanonical`, p.`hash` '.
           'FROM `rtd_piece` p '.
           'WHERE (p.`hash`) IN (?)';

    $rsm = new ResultSetMapping;
    $rsm->addScalarResult('ref', 'ref');
    $rsm->addScalarResult('hash', 'hash');
    $rsm->addScalarResult('labelCanonical', 'labelCanonical');

    $query = $this->getEntityManager()
                  ->createNativeQuery($sql, $rsm)
                  ->setParameters(array($hashes));
    return $query->getScalarResult();
}

散列在模型中自动更新,如下所示:

// Entities/Piece.class.php

private function _updateHash()
{
    $this->hash = md5($this->ref.'|'.$this->labelCanonical);
}

我的散列字段没有全文索引,因为我使用 InnoDB 引擎和 MySQL 5.5 版,据我所知,InnoDB 自 MySQL 5.6 以来仅支持全文索引。

现在没心情更新MySQL,上面跑的数据库和网站太多,更新出错后果不堪设想

但是,即使没有索引字段,性能提升也是令人难以置信的:

0.00024199485778809 (0.242) : Initialized
0.00028181076049805 (0.282) : Start doProcess
0.0003199577331543 (0.320) : Read and parse lines
0.088779926300049 (88.780) : Check existence in database
0.8656108379364 (865.611) : Process parsed data
0.94273900985718 (942.739) : Finilize
1.3771109580994 (1,377.111) : End of process
1.3795168399811 (1,379.517) : End doProcess
1.3795938491821 (1,379.594) : Return result

这是针对 table.

上的一批 1000 条记录的 650k 条记录

这次优化之前,检查100条记录需要6.7秒,所以快了大约9倍!

按照这个速度,我应该可以在 1h30-2h 内导入所有数据。

非常感谢您的帮助。