DSL和SQL查询很慢

Posted

技术标签:

【中文标题】DSL和SQL查询很慢【英文标题】:DSL, and SQL query very slow 【发布时间】:2013-10-18 12:21:34 【问题描述】:

这个问题是关于 Doctrine 和 Symfony2 的。 我使用 Doctrine DQL 进行了查询。 Doctrine 会生成这样的 SQL;

SELECT f0_.id AS id0, f0_.nom AS nom1, f0_.prenom AS prenom2, f0_.email AS email3, p1_.move_distance AS move_distance4, a2_.adresse1 AS adresse15, a2_.adresse2 AS adresse26, p3_.nom AS nom7, v4_.nom AS nom8, v4_.url AS url9, v4_.cp AS cp10, v4_.insee AS insee11, v4_.lat AS lat12, v4_.lng AS lng13, COUNT(f0_.id) AS sclr17
FROM person_teacher p1_
INNER JOIN fos_user f0_ ON p1_.id = f0_.id
LEFT JOIN person_lesson p7_ ON f0_.id = p7_.person_id
LEFT JOIN lesson l6_ ON l6_.id = p7_.lesson_id AND (l6_.id = 1)
LEFT JOIN person_teacher_language p9_ ON p1_.id = p9_.personteacher_id
LEFT JOIN language l8_ ON l8_.id = p9_.language_id AND (l8_.id = 1)
LEFT JOIN note_value n10_ ON p1_.id = n10_.personTeacher_id
LEFT JOIN pays p3_ ON f0_.id_pays = p3_.id
LEFT JOIN note n5_ ON n10_.id_note = n5_.id
LEFT JOIN person_teacher_adresse p11_ ON p1_.id = p11_.personteacher_id
LEFT JOIN adresse a2_ ON a2_.id = p11_.adresse_id
LEFT JOIN ville v4_ ON a2_.id_ville = v4_.id
GROUP BY f0_.id LIMIT 2147483647 OFFSET 0;

问题在于这些连接:

LEFT JOIN person_lesson p7_ ON f0_.id = p7_.person_id
LEFT JOIN lesson l6_ ON l6_.id = p7_.lesson_id AND (l6_.id = 1)
LEFT JOIN person_teacher_language p9_ ON p1_.id = p9_.personteacher_id
LEFT JOIN language l8_ ON l8_.id = p9_.language_id AND (l8_.id = 1)

如果我删除它们,请求就会起作用。长请求,但有效。 通过连接,请求是无限的(mysql 在 500 万之后使用了 99.9% 的 CPU 时间)或者可能很长,但无论如何,太长了

如何优化这个查询?

(PS :我认为AND (l6_.id = 1)AND (l8_.id = 1) 会充当“过滤器”并立即删除不必要的行,但是不,这会使事情变得更糟:如果我删除这些条件并添加where 会更快最后的子句,例如:WHERE (l6_.id = 1) AND (l8_.id = 1))

这是我的 DQL 代码:

$retour = $this->createQueryBuilder('p')
    ->select(array(
        'p.id',
        'p.nom',
        'p.prenom',
        'p.email',
        'p.moveDistance',
        'a.adresse1',
        'a.adresse2',
        'pn.nom as pays',
        'v.nom AS ville_nom',
        'v.url',
        'v.cp',
        'v.insee',
        'v.lat',
        'v.lng',
        'ROUND(' .
            $mul.' * ' .
            'ACOS( ' .
                'COS( RADIANS( '.$lat.')) * '.
                'COS( RADIANS( v.lat  )) * '.
                'COS( RADIANS( v.lng )-radians('.$lng.')) + '.
                'SIN( RADIANS( '.$lat.' )) * '.
                'SIN( RADIANS( v.lat )) ' .
            ')'.
        ',2) AS distance',
        ($in_kilometers?'\'km\'':'\'miles\'').' AS unit',
        'ROUND( AVG(n.importance), 1) AS importance',
        'COUNT(p.id) AS total'
    ))
    ->leftJoin('p.noteValues', 'nv')
    ->leftJoin('p.paysNaissance', 'pn')
    ->leftJoin('nv.note', 'n')
    ->leftJoin('p.adresses', 'a')
    ->leftJoin('a.ville', 'v');
/* (!) Optimizer: find out why if I do a join "ON"
 * it endlessly query. I did classical "join" then a "WHERE"
 * at the end. Find out why this method is faster:
 */
if ($lesson_id>0) 
    $retour = $retour
        ->leftJoin('p.lessons', 'le');

if ($language_id>0) 
    $retour = $retour
        ->leftJoin('p.languages', 'ln');

if (($lesson_id>0) && ($language_id>0)) 
    $retour = $retour
        ->where('le.id = :lesson_id')
        ->andWhere('ln.id = :language_id');

elseif ($lesson_id>0) 
    $retour = $retour
        ->where('le.id = :lesson_id');

elseif ($language_id>0) 
    $retour = $retour
        ->where('ln.id = :language_id');

$retour = $retour
    ->groupBy('p.id')
    ->having('distance>:dmin')
    ->andHaving('distance<=:dmax')
    ->addOrderBy($order_by_1, $order_sens_1)
    ->addOrderBy($order_by_2, $order_sens_2);

$params=array(
    'dmin' => $distance_min,
    'dmax' => $distance_max
);
if ($lesson_id>0) 
    $params['lesson_id']= $lesson_id;

if ($language_id>0) 
    $params['language_id']= $language_id;

$retour = $retour->setParameters($params);
$retour = $retour
    ->setFirstResult( $offset )
    ->setMaxResults( $limit );
return $retour;

【问题讨论】:

您能出示您原来的 DQL 查询吗?你不应该使用LEFT INNER JOIN 而不是LEFT JOIN 吗?为什么LIMIT 设置为2147483647 LIMIT 设置为巨大的限制,因为有时我需要它作为参数,有时不需要任何限制,所以我将它设置为巨大的数字。此外,如果您想始终使用OFFSET(就像我一样),LIMIT 成为强制性的。 请问您为什么使用AND (l6_.id = 1)AND (l8_.id = 1)?如果id 与 1 不同,languagelesson 将为空,但查询仍将返回该行。如果您需要此行为,则应在代码中执行,否则应在 where 子句中执行。 DQL 查询是什么样的?此外,您可能希望将查询拆分为多个查询。 好了,我已经更新了完整的 DQL。 【参考方案1】:

我建议在选择的 FROM 部分之后放置具有更多行的表, 并以这种方式更改“LEFT JOIN course l6_ ON l6_.id = p7_.lesson_id AND (l6_.id = 1)”行:看来您不需要加入 p7,因为您强制成为 l6_.id= 1,所以我改成这个

'LEFT JOIN 课程 l6_ON (l6_.id = 1)'

希望对您有所帮助。

【讨论】:

【参考方案2】:

在不查看 MySQL 表定义的情况下,为了加快查询速度,请确保您已在连接中涉及的每个列上定义了索引,否则 mysql 必须评估未命中索引的表的每条记录。

请为您的问题添加更多详细信息,我将编辑答案以反映更改。

【讨论】:

以上是关于DSL和SQL查询很慢的主要内容,如果未能解决你的问题,请参考以下文章

SQL 在 ES 查询 DSL 中具有等效关键字

在Java中查询类似DSL的机制以进行运行时sql绑定

SQL 查询在 SQL Server CE 中很慢,但在 SQL Server 中很快

sql查询很慢

SQL查询很慢。没有索引如何改进?

sql查询在Hibernate中很慢,在mysql上很快