صفحهبندیِ keyset با یادآوریِ آخرین کلیدِ دیدهشده، صفحهٔ بعد را از «بعد از همین مقدار» میگیرد؛ OFFSETاز تعدادِ ردیفهای ابتدایِ نتیجه میگذرد. وقتی داده بینِ دو خواندن تغییر کند، این دو رفتار فرق میکنند. اما keysetبهخودیِ خود snapshotنمیسازد و تضمین نمیکند همهٔ ردیفهای تازه یا تغییرکرده دیده شوند.
ترتیبِ کامل را پیش از cursor تعیین کنیم
جدولِ آموزشی پنج ردیف دارد: شناسههای۱و۲هر دوt=۱۰،سپس۳با۲۰،۴با۳۰و۵با۴۰. هر دو ستون در این قرارداد صحیح و غیرِNULLهستند. ORDER BY t,idیک ترتیبِ کامل میدهد؛ idیکتا تساویِ زمان را میشکند. هر صفحه دو ردیف دارد و صفحهٔ اول۱و۲است.
cursorاز آخرین ردیف،زوجِ(۱۰،۲)را نگه میدارد. شرطِ صفحهٔ بعد (t,id)>(۱۰،۲)است و همان ترتیبِ صعودی را حفظ میکند. این یک مقایسهٔ چپبهراستِ tupleدرSQLiteاست؛ جهتِ نزولی،collationِ متنی یا NULLنیاز به قراردادِ جدا دارد و در کدِ نمونه پیاده نشده است.
ابتدا بدونِ تغییر کنترل کنیم
در پایگاهِ ثابت،LIMIT 2 OFFSET 2وشرطِ keysetهر دو شناسههای۳و۴را میدهند. کد پیش از شاخههای تغییر،برابریِ کاملِ این دو صفحه را assert میکند. بنابراین اختلافهای بعدی ناشی از تفاوتِ ناخواستهٔ queryیا دادهٔ اولیه نیستند.
یک مثالِ یکردیفی هم اهمیتِ idرا نشان میدهد:اگر پس از شناسهٔ۱فقطt>۱۰بگذاریم،شناسهٔ۲با زمانِ مساوی جا میافتد و۳میآید. شرطِ( t,id )>(۱۰،۱)بهدرستی۲را میدهد. یک زمانِ ثبتِ مشترک لزوماً کلیدِ یکتا نیست؛ شمارهٔ صفحه هم آن تساوی را حل نمیکند.
درج یا حذفِ قبل از cursor
| تغییرِ مستقل پس از صفحهٔ اول | OFFSETصفحهٔ دوم | keysetصفحهٔ دوم |
|---|---|---|
| درجِ۹باt=۵ | ۲و۳؛۲دوباره دیده میشود | ۳و۴ |
| حذفِ شناسهٔ۱ | ۴و۵؛۳جا میافتد | ۳و۴ |
در حالتِ درج،تعدادِ ردیفهای پیش از جایِ قبلی زیاد شده است؛ گذشتن از دو ردیف دیگر به همان نقطه نمیرسد. در حالتِ حذف،برعکس،یک ردیف از پیش از نقطه کم میشود. keysetبرای همین دو تغییرِ مشخص،با کلیدِ آخرِ دیدهشده ادامه میدهد. هر شاخه پایگاهِ حافظهایِ تازه و ورودیِ یکسان دارد.
تغییرِ خودِ ستونِ مرتبسازی چه میکند؟
در شاخهٔ سوم،شناسهٔ۱که قبلاً دیدهایم ازt=۱۰به۳۵میرود. حالا بعد ازcursorقرار میگیرد و در ادامهٔ keysetدوباره دیده میشود. در شاخهٔ چهارم،شناسهٔ۵که هنوز ندیدهایم از۴۰به۵میرود؛ پیش ازcursorقرار میگیرد و دیگر در ادامهٔ آن مسیر نمیآید.
کد فهرستِ تمامِ ردیفهای بعد ازcursorرا برای این دو حالت نگه میدارد و حضورِ دوبارهٔ۱و نبودِ۵را واقعاً کنترل میکند. این مثالها نشان میدهند «ترتیبِ یکتا» با «عضویتِ ثابتِ مجموعه» فرق دارد. درجِ ردیفِ تازه پیش ازcursorهم تضمینِ دیدهشدن در یک exportکامل نیست.
snapshot و مجوز، قراردادهای جدا هستند
آزمونِ ما تغییرها را بهترتیب میان دو queryاجرا میکند؛ تراکنشِ همزمانِ واقعی یا سطحِ isolationرا اندازه نمیگیرد. اگر هدف یک خروجیِ سازگار از وضعیتِ مشخصِ داده است،باید قراردادِ snapshotیا نسخه و محدودهٔ تغییر را جدا طراحی کرد. صرفِ داشتنِ یک cursorچنین تضمینی نمیدهد.
cursorهم مجوزِ دسترسی نیست. سمتِ خدمت هنوز باید کاربر،دامنهٔ داده و مقادیرِ مجاز را بررسی کند. این فایل endpoint،token،رمز یا دادهٔ مشتری ندارد و هیچ سامانهٔ اصلی را نمیخواند. از نتیجهٔ چند ردیفِ ساختگی دربارهٔ سرعت یا ظرفیتِ APIو پایگاهِ واقعی ادعا نمیکنیم.
با نقطهٔ ادامهٔ پردازش اشتباه نکنیم
نقطهٔ ادامهٔ سند وضعیتِ بخشهای ازپیششناسهدار را نگه میدارد؛ اینجا عضویتِ صفحهٔ بعد در جدولِ درحالِ تغییر مسئله است. مدلِ تلاش و مکالمه هم تعریفِ شناسه و زمانِ رخداد/دریافت را روشن میکند و جایِ قراردادِ مرورِ صفحه را نمیگیرد.
برای اتصالِ داده در راهکارهای اپرا،اگر دریافتِ مرحلهای لازم دارید،ترتیب،ثباتِ نسخه و رفتارِ دادهٔ دیررس را با تیم فنی مشخص کنید. این تمرین پشتیبانیِ cursorیا snapshotدر APIاپرا را وعده نمیدهد؛ خروجیِ مستقلش،فهرستِ دقیقِ ردیفهای تکرارشده یا جامانده در چهار تغییرِ معلوم است.
فایل تمرین و منبع
کدِ کاملِ محلی و کنترلهای اصیلِ تمرین · دادهٔ ساختگی و خروجیِ اجرای واقعی
منابع در 2026-11-04 خوانده شدند؛ این تاریخ، تاریخ انتشار منبع نیست. مثالها و فایلهای این راهنما آموزشی و ساختهٔ تحریریهاند.