สำหรับ 400 สถานีเท่านั้นการค้นหานี้จะเร็วขึ้นอย่างมาก :
SELECT s.station_id, l.submitted_at, l.level_sensor
FROM   station s
CROSS  JOIN LATERAL (
   SELECT submitted_at, level_sensor
   FROM   station_logs
   WHERE  station_id = s.station_id
   ORDER  BY submitted_at DESC NULLS LAST
   LIMIT  1
   ) l;
dbfiddle ที่นี่
 (เปรียบเทียบแผนสำหรับแบบสอบถามนี้ทางเลือกของ Abelisto กับต้นฉบับของคุณ)
ผลEXPLAIN ANALYZEที่ได้รับจาก OP:
 วนซ้ำ (ต้นทุน = 0.56..356.65 แถว = ความกว้าง 102 = 20) (เวลาจริง = 0.034..0.979 แถว = 98 ลูป = 1)
   -> Seq สแกนสถานี s (ราคา = 0.00..3.02 แถว = 102 กว้าง = 4) (เวลาจริง = 0.009..0.016 แถว = 102 ลูป = 1)
   -> จำกัด (ค่า = 0.56..3.45 แถว = 1 ความกว้าง = 16) (เวลาจริง = 0.009..0.009 แถว = 1 ห่วง = 102)
         -> การสแกนดัชนีโดยใช้ station_id__submitted_at บน station_logs (ราคา = 0.56..664062.38 แถว = 230223 กว้าง = 16) (เวลาจริง = 0.009 $
               ดัชนี Cond: (station_id = s.id)
 เวลาในการวางแผน: 0.542 ms
 เวลาดำเนินการ: 1.013 ms   - !!
เพียงดัชนีstation_id__submitted_atที่คุณต้องมีหนึ่งคนที่คุณสร้าง: UNIQUEจำกัดuniq_sid_satยังไม่ทำงานโดยทั่วไป การบำรุงรักษาทั้งคู่ดูเหมือนว่าเป็นการสิ้นเปลืองพื้นที่ดิสก์และประสิทธิภาพการเขียน
ฉันจะเพิ่มNULLS LASTไปORDER BYในแบบสอบถามเพราะไม่ได้กำหนดไว้submitted_at NOT NULLเป็นการดีถ้ามีให้เพิ่มNOT NULLข้อ จำกัด ในคอลัมน์submitted_atให้ลบดัชนีเพิ่มเติมและลบออกNULLS LASTจากแบบสอบถาม
หากsubmitted_atทำได้ให้NULLสร้างUNIQUEดัชนีนี้เพื่อแทนที่ทั้งดัชนีปัจจุบันและข้อ จำกัด ที่ไม่ซ้ำกัน:
CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);
พิจารณา:
นี่คือสมมติว่าตารางแยกต่างหากที่stationมีหนึ่งแถวต่อเกี่ยวข้องstation_id(โดยทั่วไปคือ PK) - ซึ่งคุณควรมีวิธีใดวิธีหนึ่ง ถ้าคุณไม่มีมันสร้างมันขึ้นมา อีกครั้งอย่างรวดเร็วด้วยเทคนิค rCTE นี้:
CREATE TABLE station AS
WITH RECURSIVE cte AS (
   (
   SELECT station_id
   FROM   station_logs
   ORDER  BY station_id
   LIMIT  1
   )
   UNION ALL
   SELECT l.station_id
   FROM   cte c
   ,      LATERAL (   
      SELECT station_id
      FROM   station_logs
      WHERE  station_id > c.station_id
      ORDER  BY station_id
      LIMIT  1
      ) l
   )
TABLE cte;
ฉันใช้มันในซอเช่นกัน คุณสามารถใช้แบบสอบถามที่คล้ายกันเพื่อแก้ไขงานของคุณโดยตรงโดยไม่ต้องมีstationตาราง - หากคุณไม่สามารถสร้างมันได้
คำแนะนำโดยละเอียดคำอธิบายและทางเลือก:
ปรับดัชนีให้เหมาะสม
คำถามของคุณน่าจะเร็วมาก เฉพาะในกรณีที่คุณยังต้องการเพิ่มประสิทธิภาพการอ่าน ...
มันอาจจะทำให้ความรู้สึกที่จะเพิ่มlevel_sensorเป็นคอลัมน์สุดท้ายเพื่อจัดทำดัชนีเพื่อให้การสแกนดัชนีเท่านั้นเช่นความเห็น joanolo 
คอนดิชั่น:มันทำให้ดัชนีมีขนาดใหญ่ขึ้นซึ่งเพิ่มค่าใช้จ่ายเล็กน้อยสำหรับการสืบค้นทั้งหมดที่ใช้ 
Pro:หากคุณได้รับการสแกนดัชนีเท่านั้นจริง ๆ แล้วแบบสอบถามในมือไม่จำเป็นต้องไปที่หน้าฮีปเลยซึ่งทำให้เร็วขึ้นเป็นสองเท่า แต่นั่นอาจเป็นกำไรที่ไร้สาระสำหรับการค้นหาที่รวดเร็วมากในขณะนี้
อย่างไรก็ตามฉันไม่คาดหวังว่าจะทำงานให้กับกรณีของคุณ คุณพูดถึง:
  ... รอบ 20k station_idแถวต่อวันต่อ
โดยทั่วไปแล้วจะระบุการโหลดการเขียนที่ไม่หยุดหย่อน (1 ต่อstation_idทุก 5 วินาที) และคุณมีความสนใจในแถวล่าสุด การสแกนแบบดัชนีเท่านั้นทำงานได้เฉพาะกับเพจฮีพที่สามารถเห็นได้กับธุรกรรมทั้งหมด (บิตในแผนที่การมองเห็นถูกตั้งค่าไว้) คุณจะต้องเรียกใช้การVACUUMตั้งค่าที่ก้าวร้าวอย่างมากสำหรับตารางเพื่อให้ทันกับการโหลดการเขียนและมันจะยังไม่ทำงานส่วนใหญ่ หากสมมติฐานของฉันถูกต้องการสแกนเฉพาะดัชนีจะไม่เพิ่มลงlevel_sensorในดัชนี
OTOH ถ้าสมมติฐานของฉันค้างไว้และตารางของคุณมีการเจริญเติบโตที่ใหญ่มากเป็นดัชนี Brinอาจช่วย ที่เกี่ยวข้อง:
หรือยิ่งเชี่ยวชาญและมีประสิทธิภาพยิ่งขึ้น: ดัชนีบางส่วนสำหรับเฉพาะส่วนเพิ่มเติมล่าสุดที่จะตัดแถวจำนวนมากที่ไม่เกี่ยวข้องออก:
CREATE INDEX station_id__submitted_at_recent_idx ON station_logs(station_id, submitted_at DESC NULLS LAST)
WHERE submitted_at > '2017-06-24 00:00';
เลือกเวลาประทับที่คุณรู้ว่าต้องมีแถวที่อายุน้อยกว่า คุณต้องเพิ่มWHEREเงื่อนไขการจับคู่ให้กับแบบสอบถามทั้งหมดเช่น:
...
WHERE  station_id = s.station_id
AND    submitted_at > '2017-06-24 00:00'
...
คุณต้องปรับดัชนีและแบบสอบถามเป็นครั้งคราว 
คำตอบที่เกี่ยวข้องพร้อมรายละเอียดเพิ่มเติม: