优化 SQL 查询以从大量 MySQL 数据库中获取数据

Posted

技术标签:

【中文标题】优化 SQL 查询以从大量 MySQL 数据库中获取数据【英文标题】:Optimizing the SQL Query to get data from large amount MySQL database 【发布时间】:2017-11-08 08:04:19 【问题描述】:

我在从大量 mysql 数据库中获取数据时遇到问题。

使用下面的代码可以获取 10K 患者和 5K 预约的列表,这是我们的测试服务器。

但是,在我们的实时服务器上,患者数量超过 100K,预约数量超过 300K,当我运行代码一段时间后,它会出现 500 错误。

我需要患者的治疗状态为 1 或 3 并且在上次预约后一个月内没有预约的患者列表。 (以下代码适用于少量患者和预约)

如何优化第一个数据库查询,以便在 foreach 循环中不需要第二个数据库查询?

<?php
ini_set('memory_limit', '-1');
ini_set('max_execution_time', 0);

require_once('Db.class.php');

$patients = $db->query("
SELECT
    p.id, p.first_name, p.last_name, p.phone, p.mobile,
    LatestApp.lastAppDate
FROM
    patients p
LEFT JOIN (SELECT patient_id, MAX(start_date) AS lastAppDate FROM appointments WHERE appointment_status = 4) LatestApp ON p.id = LatestApp.patient_id
WHERE
    p.patient_treatment_status = 1 OR p.patient_treatment_status = 3
ORDER BY
    p.id
");

foreach ($patients as $row) 
    $one_month_after_the_last_appointment = date('Y-m-d', strtotime($row['lastAppDate'] . " +1 month"));
    $appointment_check = $db->single("SELECT COUNT(id) FROM appointments WHERE patient_id = :pid AND appointment_status = :a0 AND (start_date >= :a1 AND start_date <= :a2)", array("pid"=>"$row['id']","a0"=>"1","a1"=>"$row['lastAppDate']","a2"=>"$one_month_after_the_last_appointment"));

    if($appointment_check == 0)
        echo $patient_id = $row['id'].' - '.$row['lastAppDate'].' - '.$one_month_after_the_last_appointment. '<br>';
    

?>

【问题讨论】:

如果是我,我会暂时摆脱所有的 php,而专注于 sql。如果您同意,请参阅meta.***.com/questions/333952/… 理想情况下,您不应运行两个查询——尤其是循环中的一个。 我认为速度问题与子查询和LEFT JOIN有关。我认为您最好使用 INNER JOIN 来加快查询速度,删除您的子查询,正常 JOINING 另一个表,并按约会 id (如果有)使用分组,并在使用 MAX 的选择中获取最新约会. 【参考方案1】:

首先,这个子查询可能不会像你想象的那样做。

SELECT patient_id, MAX(start_date) AS lastAppDate 
FROM appointments WHERE appointment_status = 4

如果没有 GROUP BY 子句,该子查询将简单地采用与 appointment_status=4 的所有约会中的最大值 start_date,然后任意选择一个 patient_id。要获得您想要的结果,您需要GROUP BY patient_id

对于您的整体问题,请尝试以下查询:

SELECT
    p.id, p.first_name, p.last_name, p.phone, p.mobile,
    LatestApp.lastAppDate
FROM
    patients p
INNER JOIN (
    SELECT patient_id,
    MAX(start_date) AS lastAppDate
    FROM appointments
    WHERE appointment_status = 4
    GROUP BY patient_id
) LatestApp ON p.id = LatestApp.patient_id
WHERE
    (p.patient_treatment_status = 1
    OR p.patient_treatment_status = 3)
    AND NOT EXISTS (
        SELECT 1
        FROM appointments a
        WHERE a.patient_id = p.patient_id
        AND a.appointment_status = 1
        AND a.start_date >= LatestApp.lastAppDate
        AND a.start_date < DATE_ADD(LatestApp.lastAppDate,INTERVAL 1 MONTH)
)
ORDER BY
    p.id

添加以下索引,如果它尚不存在:

ALTER TABLE appointments
ADD INDEX (`patient_id`, `appointment_status`, `start_date`)

报告其执行情况以及数据是否正确。提供SHOW CREATE TABLE patientSHOW CREATE TABLE appointments 以获得与性能相关的进一步帮助。

另外,试试上面没有AND NOT EXISTS 子句的查询,以及您使用的第二个查询。在这种情况下,运行 2 个查询可能比尝试同时运行它们更快。

请注意,我使用INNER JOIN 查找最新约会。这将导致所有从未预约过的患者不包括在查询中。如果您需要添加这些,只需将通过从从未预约过的患者中选择的结果进行合并即可。

【讨论】:

@RickJames 你确定你没有忽略我在派生表中添加了GROUP BY patient_id 的事实吗?否则,我会同意你的。 糟糕;第一个派生表的更正建议:INDEX(appointment_status, patient_id, start_date).

以上是关于优化 SQL 查询以从大量 MySQL 数据库中获取数据的主要内容,如果未能解决你的问题,请参考以下文章

MySQL删除千万级数据量导致的慢查询优化

Oracle大量数据查询优化

MySQL explain根据查询计划去优化SQL语句

MySQL数据库Day03-数据库MySQL的优化

MySQL数据库Day03-数据库MySQL的优化

MySQL数据库Day03-数据库MySQL的优化