1. مفهوم و کاربرد ایندکس

1.1 چیستی ایندکس

ایندکس یک ساختار داده استفاده شده در سیستم مدیریت پایگاه‌داده برای افزایش سرعت عملیات بازیابی داده‌ها است. این می‌تواند به عنوان یک "فهرست" در پایگاه‌داده مشاهده شود که امکان لوکیشن سریع داده‌ها در یک جدول داده را فراهم می‌کند، از جستجوی کامل جدول خودداری کرده و بهبود چشمگیری در کارایی پرس و جو ایجاد می‌کند. در کاربردهای عملی، استفاده صحیح از ایندکس‌ها می‌تواند به طور چشمگیری عملکرد پایگاه‌داده را بهبود بخشد.

1.2 انواع ایندکس‌ها

انواع مختلفی از ایندکس‌ها وجود دارند، هر یک با طراحی و بهینه‌سازی خود برای سناریوهای مختلف کاربرد:

  • ایندکس تک‌فیلد: یک ایندکس که فقط یک فیلد را شامل می‌شود، مناسب برای سناریوهایی است که وابستگی به یک شرط واحد برای پرس و جوهای سریع داشته باشند.

  • ایندکس ترکیبی: یک ایندکس که شامل چندین فیلد است و بهینه‌سازی برای پرس و جوهایی که این فیلدها را دربرمی‌گیرند را فراهم می‌کند.

  • ایندکس یکتا: اطمینان از یکتایی فیلدهای ایندکس را تضمین می‌کند و وجود مقادیر تکراری را ناممکن می‌سازد. ایندکس‌های یکتا می‌توانند تک‌فیلد یا ترکیبی باشند.

2. تعریف ایندکس

2.1 تعریف ایندکس فیلد

ایجاد یک ایندکس تک‌فیلد شامل ایجاد یک ایندکس بر روی ستون خاص یک جدول داده است که با استفاده از متد Fields قابل دستیابی است. مثال زیر نشان می‌دهد چگونه می‌توان یک ایندکس بر روی فیلد phone از موجودیت User ایجاد کرد.

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("phone"),
    }
}
func (User) Indexes() []ent.Index {
    // ایجاد یک ایندکس تک‌فیلد.
    return []ent.Index{
        index.Fields("phone"),
    }
}

در این نمونه کد، فیلد phone ایندکس شده است. این امکان را به سیستم می‌دهد تا از ایندکس برای جستجوهای سریع هنگام پرس و جوی فیلد phone استفاده کند.

2.2 تعریف ایندکس یکتا

یک ایندکس یکتا اطمینان از یکتایی داده‌ها در ستون‌های ایندکس‌شده را ایجاد می‌کند. این می‌تواند با افزودن متد Unique به فیلد ایجاد شود. مثالهای زیر نشان می‌دهند چگونه می‌توان برای فیلدهای تک و چندین ایندکس‌های یکتا ایجاد کرد.

ایجاد یک ایندکس یکتا برای یک فیلد:

func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("email").Unique(),
    }
}

در این مثال، فیلد email به عنوان یکتا مشخص شده است که اطمینان می‌دهد هر ایمیل کاربر در پایگاه‌داده یکتا باشد.

ایجاد یک ایندکس یکتا برای ترکیبی از چندین فیلد:

func (User) Indexes() []ent.Index {
    return []ent.Index{
        // ایجاد یک ایندکس یکتا برای چندین فیلد.
        index.Fields("first_name", "last_name").Unique(),
    }
}

این کد یک ایندکس یکتای ترکیبی برای ترکیب first_name و last_name ایجاد می‌کند که جلوگیری از وقوع رکوردهای دارای مقادیر یکسان در هر دو فیلد را فراهم می‌کند.

2.3 تعریف ایندکس ترکیبی

هنگامی که شرایط پرس و جو از چندین فیلد تشکیل شود، ایندکس ترکیبی می‌تواند مؤثر باشد. ایندکس ترکیبی داده‌ها را بر اساس ترتیب در ایندکس ذخیره می‌کند. ترتیب ایندکس تأثیر زیادی بر کارایی پرس و جو دارد؛ بنابراین هنگام تعریف ایندکس ترکیبی، باید براساس الگوی پرس و جو ترتیب فیلدها را تعیین کرد.

در زیر یک مثال از ایجاد یک ایندکس ترکیبی آورده شده است:

func (User) Indexes() []ent.Index {
    return []ent.Index{
        // ایجاد یک ایندکس ترکیبی با چندین فیلد.
        index.Fields("country", "city"),
    }
}

در این مثال، یک ایندکس ترکیبی بر روی فیلدهای country و city ایجاد شده است. این به معنای این است که هنگام انجام عملیات پرس و جو که این دو فیلد را دربرمی‌گیرند، پایگاه‌داده می‌تواند به سرعت داده‌هایی را که شرایط را برآورده می‌کنند لوکیت کند.

ایندکس ترکیبی نه تنها کارایی پرس و جو را افزایش می‌دهد بلکه عملیات مرتب‌سازی بر پایه ایندکس را نیز پشتیبانی می‌کند و کارایی بازیابی داده‌ها را بهبود می‌بخشد. هنگام طراحی یک ایندکس ترکیبی، معمول است که فیلدها با انتخاب‌پذیری بالاتر را در جلوی ایندکس قرار دهیم تا بهینه‌ساز پایگاه‌داده بتواند ایندکس را بهتر به کار ببرد.

۳. اندیس‌های لبه

در چارچوب ent، اندیس‌های لبه یک روش برای تعریف اندیس‌ها از طریق لبه‌ها (ارتباط‌ها) هستند. این مکانیزم معمولاً برای اطمینان از یکتایی فیلدها تحت روابط خاص استفاده می‌شود. به عنوان مثال، اگر مدل پایگاه داده شما شهرها و خیابان‌ها را شامل شود و هر نام خیابان زیر یک شهر نیاز به یکتا بودن داشته باشد، می‌توان از اندیس‌های لبه برای دستیابی به این هدف استفاده کرد.

// فایل ent/schema/street.go شما ساختار موجودیت خیابان را تعریف می‌کند.
نوع Street struct {
    ent.Schema
}

کاربرد (Street) Fields() []ent.Field {
    // تعریف فیلدها
    برگردان []ent.Field{
        field.String("name"),
    }
}

کاربرد (Street) Edges() []ent.Edge {
    // تعریف رابطه لبه با شهر، جایی که خیابان به یک شهر تعلق دارد.
    برگردان []ent.Edge{
        edge.From("city", City.Type).
            Ref("streets").
            Unique(),
    }
}

کاربرد (Street) Indexes() []ent.Index {
    // ایجاد یک اندیس یکتا از طریق لبه برای اطمینان از یکتایی نام خیابان‌ها در یک شهر مشابه.
    برگردان []ent.Index{
        index.Fields("name").
            Edges("city").
            Unique(),
    }
}

در این مثال، ما یک موجودیت Street ایجاد می‌کنیم و آن را با موجودیت City ارتباط می‌دهیم. با تعریف یک اندیس لبه در متد Indexes موجودیت Street، اطمینان حاصل می‌کنیم که نام هر خیابان تحت یک شهر یکتا است.

فصل ۵: گزینه‌های پیشرفته اندیس

۵.۱ اندیس‌های متن کامل و هش

اندیس متن کامل و اندیس هش دو نوع اندیس منحصر به فرد در MySQL و PostgreSQL هستند و برای سناریوهای مختلف بهینه‌سازی پرس و جو استفاده می‌شوند.

اندیس متن کامل معمولاً برای جستجوی داده‌های متنی استفاده می‌شود، به ویژه زمانی که نیاز به انجام جستجوهای پیچیده مانند جستجوی مطابقت کلمه دارید. هر دو پایگاه داده MySQL و PostgreSQL از این نوع اندیس‌گذاری پشتیبانی می‌کنند. به عنوان مثال، در MySQL، می‌توانید یک اندیس متن کامل را به این شکل تعریف کنید:

// فایل ent/schema/user.go ساختار موجودیت کاربر را تعریف می‌کند
کاربر (User) Indexes() []ent.Index {
    // ایجاد یک اندیس متن کامل با استفاده از دسته FULLTEXT در MySQL
    برگردان []ent.Index{
        index.Fields("description").
            Annotations(entsql.IndexTypes(map[string]string{
                dialect.MySQL: "FULLTEXT",
            })),
    }
}

اندیس هش به ویژه برای پرس و جوی برابری مناسب است و از مرتب‌سازی و پرس و جوهای محدوده‌ای پشتیبانی نمی‌کند. در PostgreSQL، می‌توانید از یک اندیس هش به این شکل استفاده کنید:

کاربر (User) Indexes() []ent.Index {
    // تعریف یک اندیس از نوع HASH
    برگردان []ent.Index{
        index.Fields("c4").
            Annotations(entsql.IndexType("HASH")),
    }
}

۵.۲ اندیس‌های جزئی و پیشوندهای اندیس

اندیس جزئی یک نوع اندیس است که فقط ردیف‌ها را در یک جدول که شرایط مشخصی را برآورده می‌کنند، نشانه‌گذاری می‌کند. در SQLite و PostgreSQL، می‌توانید از شرط WHERE برای ایجاد اندیس‌های جزئی استفاده کنید.

به عنوان مثال، تعریف یک اندیس جزئی در PostgreSQL:

کاربر (User) Indexes() []ent.Index {
    // ایجاد یک اندیس جزئی روی فیلد "nickname"، شامل تنها ردیف‌هایی که "active" درست است
    برگردان []ent.Index{
        index.Fields("nickname").
            Annotations(
                entsql.IndexWhere("active"),
            ),
    }
}

پیشوند اندیس به ویژه برای فیلدهای متنی، به ویژه در MySQL، مفید است. این امر می‌تواند زمان ایجاد اندیس را کوتاه کند، فضای اشغال شده توسط اندیس را کاهش دهد، و همچنین عملکرد خوبی ارائه دهد. همانطور که در زیر نشان داده شده است، برای MySQL می‌توانید یک اندیس با پیشوند تعریف کنید:

کاربر (User) Indexes() []ent.Index {
    // ایجاد یک اندیس با استفاده از پیشوند اندیس
    برگردان []ent.Index{
        index.Fields("description").
            Annotations(entsql.Prefix(128)),
    }
}

۵.۳ حاشیه‌نویسی اندیس و سفارشی‌سازی

در ent، Annotations یک ویژگی است که به توسعه دهندگان امکان سفارشی‌سازی اندیس‌ها را می‌دهد. شما می‌توانید نوع اندیس را تعریف کنید، قواعد مرتب‌سازی را تنظیم کنید و غیره.

به عنوان مثال، کد زیر نحوه مشخص کردن قواعد مرتب‌سازی ستون‌ها در یک اندیس را نشان می‌دهد:

کاربر (User) Indexes() []ent.Index {
    برگردان []ent.Index{
        // استفاده از حاشیه‌نویسی برای تعریف قواعد مرتب‌سازی ستون‌های اندیس
        index.Fields("c1", "c2", "c3").
            Annotations(entsql.DescColumns("c1", "c2")),
    }
}

با ویژگی حاشیه‌نویسی، توسعه دهندگان می‌توانند به طور انعطاف‌پذیر اندیس‌ها را سفارشی کنند تا عملکرد و ساختار پایگاه داده را بهینه‌سازی کنند.