اليوم لن تكون هناك حالات معقدة وخوارزميات SQL معقدة. سيكون كل شيء بسيطًا جدًا ، على مستوى الكابتن. الدليل - نقوم بمراجعة سجل الأحداث مع التصنيف حسب الوقت.أي أن هناك لوحة في القاعدة events
، وحقلها ts
هو نفس الوقت الذي نريد فيه عرض هذه السجلات بطريقة منظمة تمامًا:CREATE TABLE events(
id
serial
PRIMARY KEY
, ts
timestamp
, data
json
);
CREATE INDEX ON events(ts DESC);
من الواضح أنه لن يكون لدينا عشرات المدخلات ، لذلك سنحتاج إلى نوع من التنقل في الصفحة .# 0 "أنا مجرم في أمي"
cur.execute("SELECT * FROM events;")
rows = cur.fetchall();
rows.sort(key=lambda row: row.ts, reverse=True);
limit = 26
print(rows[offset:offset+limit]);
إنها ليست مزحة - نادرًا ، ولكنها موجودة في البرية. في بعض الأحيان بعد العمل مع ORM ، قد يكون من الصعب التبديل إلى عمل "مباشر" مع SQL.ولكن دعنا ننتقل إلى مشاكل أكثر شيوعًا وأقل وضوحًا.رقم 1. عوض
SELECT
...
FROM
events
ORDER BY
ts DESC
LIMIT 26 OFFSET $1;
26? . , 25 , 1, , - .
, «» , . PostgreSQL , , — .
وأثناء عرض السجل في واجهة التطبيق يتم تنفيذه على أنه التبديل بين "الصفحات" المرئية ، لا يلاحظ أحد لفترة طويلة أي شيء مريب. بالضبط حتى اللحظة التي لا يقرر فيها UI / UX في النضال من أجل الراحة إعادة إنشاء الواجهة إلى "التمرير اللامتناهي" - أي ، يتم رسم جميع إدخالات التسجيل في قائمة واحدة يمكن للمستخدم تحريكها لأعلى ولأسفل.والآن ، خلال الاختبار التالي ، يتم اكتشاف إدخالات مكررة في التسجيل. لماذا ، لأن الجدول يحتوي على فهرس عادي (ts)
يستند إليه استعلامك؟بالضبط لأنك لم تفكر في ما ts
ليس مفتاحًا فريدًا في هذا الجدول. في الواقع ، معانيه ليست فريدة، مثل أي "وقت" في ظروف حقيقية - لهذا السبب ، فإن "السجل نفسه" في استعلامين مجاورين "ينتقل" بسهولة من صفحة إلى أخرى نظرًا لترتيب نهائي مختلف كجزء من فرز نفس القيمة الأساسية.في الواقع ، يتم إخفاء المشكلة الثانية هنا أيضًا ، والتي يصعب ملاحظتها - لن يتم عرض بعض الإدخالات على الإطلاق! بعد كل شيء ، أخذت السجلات "المكررة" مكان شخص ما. يمكن العثور على شرح مفصل مع صور جميلة هنا .توسيع المؤشر
يدرك المطور الماكر أنك بحاجة إلى جعل مفتاح الفهرس فريدًا ، والطريقة الأسهل هي توسيعه بحقل فريد متعمد ، وهو PK مثالي لـ:CREATE UNIQUE INDEX ON events(ts DESC, id DESC);
ويتغير الطلب:SELECT
...
ORDER BY
ts DESC, id DESC
LIMIT 26 OFFSET $1;
# 2 الانتقال إلى "المؤشرات"
بعد مرور بعض الوقت ، يأتي DBA إليك وهو "سعيد" لأن طلباتك تقوم بتحميل الخادم بشكل هزلي مع مجموعات OFFSET التي تجرها الخيول ، وبشكل عام ، حان الوقت للتبديل إلى التنقل من آخر قيمة معروضة . يتغير طلبك مرة أخرى:SELECT
...
WHERE
(ts, id) < ($1, $2)
ORDER BY
ts DESC, id DESC
LIMIT 26;
لقد تنفست الصعداء قبل أن تأتي ...# 3 تنظيف الفهرس
لأنه في يوم من الأيام ، قرأ DBA مقالًا حول العثور على فهارس غير فعالة وأدركت أن الطابع الزمني "الأخير" ليس جيدًا . وقد أتى إليك مرة أخرى - الآن مع فكرة أن هذا المؤشر يجب أن يتحول إلى (ts DESC)
.ولكن ماذا تفعل مع المشكلة الأولية "القفز" بين السجلات؟ .. وكل شيء بسيط - تحتاج إلى اختيار الكتل بعدد غير محدود من السجلات!بشكل عام ، من يمنعنا من قراءة ليس "26 بالضبط" ، ولكن "ليس أقل من 26"؟ على سبيل المثال ، بحيث توجد في الكتلة التالية سجلات ذات قيم مختلفة بشكل واضحts
- فلن تكون هناك مشاكل في "القفز" بين الكتل!هيريس كيفية القيام بذلك:SELECT
...
WHERE
ts < $1 AND
ts >= coalesce((
SELECT
ts
FROM
events
WHERE
ts < $1
ORDER BY
ts DESC
LIMIT 1 OFFSET 25
), '-infinity')
ORDER BY
ts DESC;
ما الذي يجري هنا؟- ننزل 25 سجلاً ونحصل على قيمة "الحدود"
ts
. - إذا لم يكن هناك شيء بالفعل ، فاستبدل القيمة NULL بـ
-infinity
. - اطرح الجزء الكامل من القيم بين القيمة المستلمة
ts
والمعلمة $ 1 التي تم تمريرها من الواجهة (القيمة المرسومة "الأخيرة" السابقة). - إذا تم إرجاع كتلة تحتوي على أقل من 26 إدخالاً ، فهذا هو الأخير.
أو نفس الصورة:نظرًا لأن نموذجنا لا يحتوي الآن على "بداية" محددة ، فلا شيء يمنعنا من "عكس" هذا الاستعلام في الاتجاه المعاكس وتنفيذ التحميل الديناميكي لكتل البيانات من "النقطة المرجعية" في كلا الاتجاهين - لأسفل وأعلى.تعليق
- , , « ». Index Only Scan.
- , ,
ts
, . — « 00:00:00.000», . , . , .