如何使用多个连接优化查询?

时间:2016-08-12 17:30:51

标签: mysql database query-optimization

我有简单但很长的查询,它计算结果的内容大约需要14秒。主表上的计数本身不到一秒钟,但在多次加入后,延迟太高,如下所示

Select  Count(Distinct visits.id) As Count_id
    From  visits
    Left Join  clients_locations  ON visits.client_location_id = clients_locations.id
    Left Join  clients  ON clients_locations.client_id = clients.id
    Left Join  locations  ON clients_locations.location_id = locations.id
    Left Join  users  ON visits.user_id = users.id
    Left Join  potentialities  ON clients_locations.potentiality = potentialities.id
    Left Join  classes  ON clients_locations.class = classes.id
    Left Join  professions  ON clients.profession_id = professions.id
    Inner Join  specialties  ON clients.specialty_id = specialties.id
    Left Join  districts  ON locations.district_id = districts.id
    Left Join  provinces  ON districts.province_id = provinces.id
    Left Join  locations_types  ON locations.location_type_id = locations_types.id
    Left Join  areas  ON clients_locations.area_id = areas.id
    Left Join  calls  ON calls.visit_id = visits.id 

解释的输出是

+---+---+---+---+---+---+---+---+---+---+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+---+---+---+---+---+---+---+---+---+---+
| 1 | SIMPLE | specialties | index | PRIMARY | specialty_name | 52 | NULL | 53 | Using index |
| 1 | SIMPLE | clients | ref | PRIMARY,specialty | specialty | 4 | crm_db.specialties.id | 143 |  |
| 1 | SIMPLE | clients_locations | ref | PRIMARY,client_id | client_id | 4 | crm_db.clients.id | 1 |  |
| 1 | SIMPLE | locations | eq_ref | PRIMARY | PRIMARY | 4 | crm_db.clients_locations.location_id | 1 |  |
| 1 | SIMPLE | districts | eq_ref | PRIMARY | PRIMARY | 4 | crm_db.locations.district_id | 1 | Using where |
| 1 | SIMPLE | visits | ref | unique_visit,client_location_id | unique_visit | 4 | crm_db.clients_locations.id | 4 | Using index |
| 1 | SIMPLE | calls | ref | call_unique,visit_id | call_unique | 4 | crm_db.visits.id | 1 | Using index |
+---+---+---+---+---+---+---+---+---+---+

更新1 上面的查询与动态where语句$sql = $sql . "Where ". $whereFilter一起使用,但我以简单的形式提交了它。所以不要认为答案只是简化联接:)

更新2 以下是动态过滤的示例

$temp = $this->province_id;
if ($temp != null) {
        $whereFilter = $whereFilter . " and provinces.id In ($temp) ";
    }

但是在启动案例中,我们的情况是没有where声明

6 个答案:

答案 0 :(得分:7)

左连接总是从第一个表返回一行,但如果有多个匹配的行,则可能返回多行。但是因为您正在计算不同的访问行,所以在连接到另一个表时,计算不同的访问次数与计算访问行数相同。因此,影响结果的唯一连接是内部连接,因此您可以完全删除所有"#34;离开连接表而不影响结果。

我的意思完全""是一些左连接表有效内连接;内部联接specialty要求联接到clients成功,因此也是内部联接,这反过来要求联接到clients_locations成功,因此也是内部联接。

您的查询(已发布)可以简化为:

Select Count(Distinct visits.id) As Count_id
From visits
Join clients_locations ON visits.client_location_id = clients_locations.id
Join clients ON clients_locations.client_id = clients.id
Join specialties ON clients.specialty_id = specialties.id

删除所有这些不必要的连接将极大地改善查询的运行时间,这不仅是因为连接的连接较少,而且因为当您认为大小是 product <时,生成的行集大小可能会很大/ em>所有表格中的匹配项(不是 sum

为获得最佳性能,请在所有id-and-fk列上创建覆盖索引:

create index visits_id_client_location_id on visits(id, client_location_id);
create index clients_locations_id_client_id on clients_locations(id, client_id);
create index clients_id_specialty_id on clients(id, specialty_id);

因此可以在可能的情况下使用仅索引扫描。我假设PK列上有索引。

答案 1 :(得分:3)

您似乎没有任何(或多次)故意过滤。如果您想知道calls中提到的访问次数,我建议:

select count(distinct c.visit_id)
from calls c;

答案 2 :(得分:3)

为了优化整个过程,您可以根据要应用的过滤器动态构建前置SQL。像:


    // base select and left join 
    $preSQL = "Select  Count(Distinct visits.id) As Count_id From  visits ";
    $preSQL .= "Left Join  clients_locations  ON visits.client_location_id = clients_locations.id ";

    // filtering by province_id
    $temp = $this->province_id;
    if ($temp != null) {
            $preSQL .= "Left Join  locations ON clients_locations.location_id = locations.id ";
            $preSQL .= "Left Join  districts ON locations.district_id = districts.id ";
            $preSQL .= "Left Join  provinces ON districts.province_id = provinces.id ";
            $whereFilter = "provinces.id In ($temp) ";
        }

    $sql = $preSQL . "Where ".   $whereFilter;
    // ...

如果您使用多个过滤器,则可以将所有内部/左侧连接字符串放在一个数组中,然后在分析请求后,您可以使用最少的连接构建$preSQL

答案 3 :(得分:1)

使用COUNT(visit_id!=“”然后1结束时的情况)作为访问。

希望这会有所帮助

答案 4 :(得分:1)

不仅仅是:

SELECT COUNT(id)
FROM visits

因为当没有匹配的客户端,...,调用和id应该是唯一的时,所有左外连接也会返回visits.id吗?

不同的提示:一个内连接也仅在客户端存在时有效。通常在需要内部连接时,它们必须尽可能高/靠近源表,因此在您的示例中,在“左连接客户端”之后的行中最好。

答案 5 :(得分:0)

我不太了解你的想法,特别是你的INNER JOIN将在INNER JOIN中转换一些LEFT,这看起来很奇怪,但让我们尝试解决方案:

通常LEFT JOIN的性能非常糟糕,我认为只有在WHERE子句中使用它们才需要它们,然后只有在你使用它们时才能用INNER JOIN包含它们。 例如:

$query = "Select Count(Distinct visits.id) As Count_id  From  visits ";

if($temp != null){
    $query .= " INNER JOIN  clients_locations  ON visits.client_location_id = clients_locations.id ";
    $query .= " INNER JOIN  locations  ON clients_locations.location_id = locations.id  ";
    $query .= " INNER JOIN  locations  ON clients_locations.location_id = locations.id ";
    $query .= " INNER JOIN  districts  ON locations.district_id = districts.id "
    $query .= " INNER JOIN  provinces  ON districts.province_id = provinces.id ";
    $whereFilter .= " and provinces.id In ($temp) ";
}

我认为这有助于您的表现,并且可以根据您的需要运作。