UUID — أو المعرّف الفريد العام — هو سلسلة 128 بت تُستخدم لتعريف أي شيء: مستخدم، طلب، ملف، سجل. تقابلها في كل مكان: قواعد البيانات، معرّفات الطلبات، روابط الملفات. ورغم أن كل UUID يبدو للوهلة الأولى كخيط عشوائي، إلا أن كل نوع يولّده بطريقة مختلفة، وهذا الاختلاف البسيط يغيّر سلوكه في قاعدة البيانات أكثر مما تتوقع.
بنية الـ UUID
أي UUID يُكتب بصيغة 8-4-4-4-12، أي 32 محرفاً سداسياً مفصولة بأربع شرطات. ما بين الشرطات ترميز مجرد سريع للرقم نفسه:
550e8400-e29b-41d4-a716-446655440000
└─8──┘ └─4─┘ └─4─┘ └─4─┘ └────12────┘
↓ ↓ ↓ ↓
time-low time-mid clock-seq node
↑
version (الرقم 4 في الموضع الثالث)
↑
variant (يبدأ بـ 8 أو 9 أو a أو b)الجزء الذي يهمّك عملياً هو محرف واحد فقط: الرقم الذي يشير إلى النسخة. هو الذي يحدّد كيف وُلّد هذا المعرّف، وبالتالي ما يضمنه لك.
الإصدار 1: مرتبط بالوقت
UUID v1 يولّد المعرّف من العنوان المادي لجهازك (MAC address) مضافاً إليه الطابع الزمني بدقة 100 نانوثانية. النتيجة: المعرّفات مرتّبة زمنياً، لكن جزئياً في ترتيب زمني — ويمكن التنبؤ بها. لهذا تُستخدم v1 في أنظمة مغلقة فقط، ولا تصلح لمعرّفات توضع في رابط عام.
الإصدار 4: العشوائية الكاملة
الأكثر استخداماً على الإطلاق. يولّد 122 بتاً عشوائياً من مولّد عشوائي آمن (cryptographically secure). لا يحمل أي معلومات عن وقت الإنشاء ولا عن الجهاز. بساطته هي قوته، وعشوائيته هي ضعفه:
- لا يمكن التنبؤ بالمعرّف ولا استنتاجه — وهذا مطلوب عند كشف المعرّفات في روابط عامة.
- لا يحمل أي معلومة زمنية، فعلى النظام أن يضيف عموداً منفصلاً للترتيب.
- عند الإدراج في فهرس (index) عشوائي، يتوزّع على شجرة B-tree، وهذا سيّئ للأداء.
الإصدار 7: الحل الوسط
الإصدار v7 هو الأحدث عملياً، ويعتمد على فكرة بسيطة: ضع الطابع الزمني في أول المعرّف، ثم أضف عشوائية بعده. النتيجة معرّف يبدو عشوائياً لكنه مرتّب زمنياً.
v4 (عشوائي) 8f14e45f-ceea-467a-9d7e-3b2a1c0d5e6f ← لا ترتيب
v7 (زمني) 018e4a2c-7d13-7f2b-a9e4-6c1d5b8f0a3e ← أول 12 محرفاً زمنية| الخاصية | v4 | v7 |
|---|---|---|
| الترتيب الزمني | لا | نعم |
| عشوائي البادئة | نعم | نعم |
| الأداء مع الفهرس | متفرّق | متسلسل |
| حجم النص (36 محرفاً) | نفسه | نفسه |
| الاعتماد التشغيلي | رسمي منذ سنوات | أحدث نسبياً |
المقارنة الحاسمة: UUID مقابل المفتاح الرقمي
أهم سؤال في هذا الموضوع ليس أي نوع تختار من UUID، بل: هل تحتاج UUID أصلاً؟ المفتاح الرقمي التلقائي (auto-increment) يفوز في كل المقاييس عدا واحد:
| المعيار | INTEGER (auto-increment) | UUID (v4) |
|---|---|---|
| الحجم على القرص | 8 بايت | 16 بايت |
| أداء الفهرس | مثالي | متفرّق |
| قبل الحفظ | غير موجود | موجود |
| دعم التوزيع | يحتاج تنسيقاً مركزياً | تولّد من أي خادم |
| تسريب المعلومات | يكشف حجم القاعدة | لا يكشف شيئاً |
ULID: البديل الذي يجمع الصفتين
ULID يحلّ مشكلة v7 بطريقة أبسط: 26 محرفاً من ترميز Base32، أول 10 منها هي الوقت بالضبط متبوعةً بـ 16 محرفاً عشوائياً. النتيجة: معرّف يُقرأ بالعين، مرتّب زمنياً، ويحمل ذاكرته في أوله.
UUID v4 : 550e8400-e29b-41d4-a716-446655440000 (36 محرفاً)
ULID : 01ARZ3NDEKTSV4RRFFQ69G5FAV (26 محرفاً)
↑ الطابع الزمني مدمج في النصمتى تستعمل كل واحد
- v4: معرّفات تُعرض في روابط عامة أو تُسجَّل من العملاء، لأن العشوائية تمنع التخمين.
- v7: معرّفات داخلية في جداول كبيرة تحتاج ترتيباً وأداء فهرسة — الخيار الافتراضي الحديث.
- ULID: تريد ترتيباً ووضوحاً في السجل مع حجم أصغر، وأن تكون المقارنة النصية مطابقة للترتيب الزمني.
- INTEGER: جدول واحد، خادم واحد، لا حاجة لتوزيع — وهذا أكثر من نصف الحالات الفعلية.
كيف تولّده
لا تحتاج مكتبة لتوليد v4 — يوفّر المتصفح ذلك بشكل أصلي وآمن:
// UUID v4 — مولّد عشوائي آمن مدمج في المتصفح
function uuidv4() {
return crypto.randomUUID();
}
// لمجموعة دفعة واحدة
const ids = Array.from({ length: 100 }, () => crypto.randomUUID());
// في Node.js
// import { randomUUID } from "node:crypto";
// const id = randomUUID();ملاحظة عن التخزين
النص الكامل (36 محرفاً) مريح للعرض، لكنه ثقيل للتخزين والفهرسة. معظم قواعد البيانات الجاهزة اليوم تدعم تخزين UUID كنوع ثنائي 16 بايت، مع دالة لتحويله عند القراءة والكتابة. هذا يوفّر حجم الفهرس ويحوّل المقارنات من نصية إلى ثنائية، وهي أسرع:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL
);
-- المعرّف الثنائي أصغر وأسرع في الفهرسة
ALTER TABLE users ALTER COLUMN id TYPE uuid USING id::uuid;