เห็นได้ชัดว่ามีหลายวิธีในการรับผลลัพธ์ที่เหมือนกันคำถามของคุณน่าจะเป็นวิธีที่มีประสิทธิภาพในการรับผลลัพธ์สุดท้ายในแต่ละกลุ่มใน MySQL หากคุณกำลังทำงานกับข้อมูลจำนวนมากและสมมติว่าคุณกำลังใช้ InnoDB กับ MySQL เวอร์ชันล่าสุด (เช่น 5.7.21 และ 8.0.4-rc) ดังนั้นอาจไม่มีวิธีที่มีประสิทธิภาพในการทำเช่นนี้
บางครั้งเราต้องทำสิ่งนี้กับตารางที่มีมากกว่า 60 ล้านแถว
สำหรับตัวอย่างเหล่านี้ฉันจะใช้ข้อมูลที่มีเพียงประมาณ 1.5 ล้านแถวที่แบบสอบถามจะต้องค้นหาผลลัพธ์สำหรับทุกกลุ่มในข้อมูล ในกรณีที่เกิดขึ้นจริงของเราเรามักจะต้องส่งคืนข้อมูลจากกลุ่มประมาณ 2,000 กลุ่ม (ซึ่งสมมุติว่าไม่ต้องการตรวจสอบข้อมูลมากนัก)
ฉันจะใช้ตารางต่อไปนี้:
CREATE TABLE temperature(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
groupID INT UNSIGNED NOT NULL,
recordedTimestamp TIMESTAMP NOT NULL,
recordedValue INT NOT NULL,
INDEX groupIndex(groupID, recordedTimestamp),
PRIMARY KEY (id)
);
CREATE TEMPORARY TABLE selected_group(id INT UNSIGNED NOT NULL, PRIMARY KEY(id));
ตารางอุณหภูมิบรรจุด้วยการสุ่มประมาณ 1.5 ล้านบันทึกและมี 100 กลุ่มที่แตกต่างกัน Selected_group มีประชากร 100 กลุ่ม (ในกรณีของเราซึ่งปกติจะน้อยกว่า 20% สำหรับทุกกลุ่ม)
เนื่องจากข้อมูลนี้มีการสุ่มหมายความว่าหลายแถวสามารถมีบันทึกไว้เหมือนกัน สิ่งที่เราต้องการคือการรับรายชื่อของกลุ่มที่เลือกทั้งหมดตามลำดับของ groupID ที่บันทึกไว้ครั้งสุดท้ายสำหรับแต่ละกลุ่มและหากกลุ่มเดียวกันมีมากกว่าหนึ่งแถวที่ตรงกันเช่นนั้นรหัสการจับคู่ล่าสุดของแถวเหล่านั้น
ถ้าสมมุติว่า MySQL มีฟังก์ชั่นสุดท้าย () ซึ่งคืนค่าจากแถวสุดท้ายในประโยค ORDER BY พิเศษแล้วเราก็ทำได้:
SELECT
last(t1.id) AS id,
t1.groupID,
last(t1.recordedTimestamp) AS recordedTimestamp,
last(t1.recordedValue) AS recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
ORDER BY t1.recordedTimestamp, t1.id
GROUP BY t1.groupID;
ซึ่งจะต้องตรวจสอบเพียง 100 แถวในกรณีนี้เนื่องจากไม่ได้ใช้ฟังก์ชัน GROUP BY ปกติใด ๆ สิ่งนี้จะดำเนินการใน 0 วินาทีและด้วยเหตุนี้จึงมีประสิทธิภาพสูง โปรดทราบว่าโดยปกติใน MySQL เราจะเห็น ORDER BY clause ตาม GROUP BY clause แต่ประโยค ORDER BY นี้จะถูกใช้เพื่อกำหนด ORDER สำหรับฟังก์ชั่นสุดท้าย () ถ้ามันเป็นหลังจาก GROUP BY แล้วมันจะทำการสั่งกลุ่ม หากไม่มี GROUP GROUP clause อยู่ค่าสุดท้ายจะเหมือนกันในแถวที่ส่งคืนทั้งหมด
อย่างไรก็ตาม MySQL ไม่มีสิ่งนี้ดังนั้นเรามาดูแนวคิดที่แตกต่างกันของสิ่งที่มันมีและพิสูจน์ว่าไม่มีสิ่งใดที่มีประสิทธิภาพ
ตัวอย่างที่ 1
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
SELECT t2.id
FROM temperature t2
WHERE t2.groupID = g.id
ORDER BY t2.recordedTimestamp DESC, t2.id DESC
LIMIT 1
);
สิ่งนี้ตรวจสอบ 3,009,254 แถวและใช้เวลาประมาณ 0.859 วินาทีใน 5.7.21 และอีกต่อไปเล็กน้อยใน 8.0.4-rc
ตัวอย่างที่ 2
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM temperature t1
INNER JOIN (
SELECT max(t2.id) AS id
FROM temperature t2
INNER JOIN (
SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
FROM selected_group g
INNER JOIN temperature t3 ON t3.groupID = g.id
GROUP BY t3.groupID
) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
GROUP BY t2.groupID
) t5 ON t5.id = t1.id;
สิ่งนี้ตรวจสอบ 1,505,331 แถวและใช้เวลา ~ 1.25 วินาทีใน 5.7.21 และอีกต่อไปเล็กน้อยใน 8.0.4-rc
ตัวอย่างที่ 3
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM temperature t1
WHERE t1.id IN (
SELECT max(t2.id) AS id
FROM temperature t2
INNER JOIN (
SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
FROM selected_group g
INNER JOIN temperature t3 ON t3.groupID = g.id
GROUP BY t3.groupID
) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
GROUP BY t2.groupID
)
ORDER BY t1.groupID;
สิ่งนี้ตรวจสอบ 3,009,685 แถวและใช้เวลาประมาณ 1.95 วินาทีใน 5.7.21 และยาวกว่าบน 8.0.4-rc เล็กน้อย
ตัวอย่างที่ 4
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
SELECT max(t2.id)
FROM temperature t2
WHERE t2.groupID = g.id AND t2.recordedTimestamp = (
SELECT max(t3.recordedTimestamp)
FROM temperature t3
WHERE t3.groupID = g.id
)
);
สิ่งนี้ทำการตรวจสอบ 6,137,810 แถวและใช้เวลาประมาณ 2.2 วินาทีใน 5.7.21 และอีกต่อไปเล็กน้อยบน 8.0.4-rc
ตัวอย่างที่ 5
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
SELECT
t2.id,
t2.groupID,
t2.recordedTimestamp,
t2.recordedValue,
row_number() OVER (
PARTITION BY t2.groupID ORDER BY t2.recordedTimestamp DESC, t2.id DESC
) AS rowNumber
FROM selected_group g
INNER JOIN temperature t2 ON t2.groupID = g.id
) t1 WHERE t1.rowNumber = 1;
สิ่งนี้ตรวจสอบ 6,017,808 แถวและใช้เวลาประมาณ 4.2 วินาทีใน 8.0.4-rc
ตัวอย่างที่ 6
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
SELECT
last_value(t2.id) OVER w AS id,
t2.groupID,
last_value(t2.recordedTimestamp) OVER w AS recordedTimestamp,
last_value(t2.recordedValue) OVER w AS recordedValue
FROM selected_group g
INNER JOIN temperature t2 ON t2.groupID = g.id
WINDOW w AS (
PARTITION BY t2.groupID
ORDER BY t2.recordedTimestamp, t2.id
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
) t1
GROUP BY t1.groupID;
สิ่งนี้ตรวจสอบ 6,017,908 แถวและใช้เวลาประมาณ 17.5 วินาทีใน 8.0.4-rc
ตัวอย่างที่ 7
SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
LEFT JOIN temperature t2
ON t2.groupID = g.id
AND (
t2.recordedTimestamp > t1.recordedTimestamp
OR (t2.recordedTimestamp = t1.recordedTimestamp AND t2.id > t1.id)
)
WHERE t2.id IS NULL
ORDER BY t1.groupID;
อันนี้พากันตลอดไปดังนั้นฉันต้องฆ่ามัน