← Назад к вопросам

В каких случаях PostgreSQL создает индекс по дефолту

2.0 Middle🔥 81 комментариев
#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Автоматическое создание индексов в PostgreSQL

PostgreSQL создаёт индексы автоматически только в определённых, строго определённых случаях. Это важно понимать для проектирования схемы базы данных и предотвращения проблем с производительностью.

Случаи автоматического создания индексов

1. Primary Key (Первичный ключ)

При создании первичного ключа PostgreSQL автоматически создаёт уникальный индекс (B-tree) для него.

# SQL
CREATE TABLE users (
    id SERIAL PRIMARY KEY,  -- Индекс создаётся автоматически
    email VARCHAR(100)
);

# Django
from django.db import models

class User(models.Model):
    id = models.AutoField(primary_key=True)  # Индекс автоматический
    email = models.EmailField()

Проверить индекс:

# SQL
\d users  -- В psql
# или
SELECT * FROM pg_indexes WHERE tablename = 'users';

2. Unique Constraint (Уникальное ограничение)

Для обеспечения уникальности PostgreSQL создаёт уникальный B-tree индекс.

# SQL
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(100) UNIQUE  -- Индекс создаётся автоматически
);

# Django
from django.db import models

class User(models.Model):
    email = models.EmailField(unique=True)  # Индекс автоматический

3. Foreign Key (Внешний ключ)

Важный момент: PostgreSQL НЕ создаёт индекс автоматически на столбец внешнего ключа. Это отличает его от MySQL (которая иногда создаёт). Однако это рекомендуется делать вручную для оптимизации JOIN операций.

# SQL — индекс НЕ создаётся автоматически
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id)  -- ИНДЕКСА НЕТ!
);

# Django — также не создаёт индекс
from django.db import models

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    # Индекс НЕ создаётся автоматически!

# Рекомендуется добавить индекс вручную
class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)

Проверка наличия индекса:

# SQL
SELECT * FROM pg_indexes WHERE tablename = 'posts' AND indexname LIKE '%user%';

4. Выражения в ограничениях (Constraint expressions)

При использовании функций в UNIQUE или CHECK ограничениях, PostgreSQL создаёт индекс на выражение.

# SQL
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(100),
    UNIQUE (LOWER(email))  -- Индекс создаётся на выражение LOWER(email)
);

Случаи, когда индексы НЕ создаются автоматически

Обычные столбцы в WHERE условиях

Постгрес НЕ создаёт индекс просто потому что вы часто фильтруете по этому столбцу.

# SQL — индекс НЕ создаётся
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    status VARCHAR(20)  -- Нет индекса!
);

# Если часто делаете:
SELECT * FROM orders WHERE status = 'completed';

# Нужно создать индекс вручную
CREATE INDEX idx_orders_status ON orders(status);

# Django
class Order(models.Model):
    status = models.CharField(max_length=20, db_index=True)  # Индекс вручную

Столбцы в JOIN условиях

# SQL — индекс НЕ создаётся
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id)  # ИНДЕКСА НЕТ!
);

# Частый JOIN
SELECT * FROM posts p
JOIN users u ON p.user_id = u.id;

# Нужен индекс вручную
CREATE INDEX idx_posts_user_id ON posts(user_id);

Столбцы в ORDER BY и GROUP BY

# SQL — индекс НЕ создаётся
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    created_at TIMESTAMP  -- Нет индекса!
);

# Если часто сортируете
SELECT * FROM products ORDER BY created_at DESC LIMIT 10;

# Нужен индекс
CREATE INDEX idx_products_created_at ON products(created_at DESC);

Полный пример миграции

# Django migration
from django.db import migrations, models

class Migration(migrations.Migration):
    initial = True
    
    dependencies = []
    
    operations = [
        # Автоматические индексы
        migrations.CreateModel(
            name='User',
            fields=[
                ('id', models.AutoField(primary_key=True)),  # Индекс автоматический
                ('email', models.EmailField(unique=True)),  # Индекс автоматический
                ('username', models.CharField(max_length=100)),  # Нет индекса!
            ],
        ),
        
        migrations.CreateModel(
            name='Post',
            fields=[
                ('id', models.AutoField(primary_key=True)),  # Индекс автоматический
                ('user', models.ForeignKey(on_delete=models.CASCADE, to='user')),  # Нет индекса!
                ('status', models.CharField(max_length=20)),  # Нет индекса!
                ('created_at', models.DateTimeField(auto_now_add=True)),  # Нет индекса!
            ],
        ),
        
        # Добавляем индексы вручную
        migrations.RunSQL(
            "CREATE INDEX idx_user_username ON user(LOWER(username));",
            reverse_sql="DROP INDEX idx_user_username;"
        ),
        
        migrations.RunSQL(
            "CREATE INDEX idx_post_user_id ON post(user_id);",
            reverse_sql="DROP INDEX idx_post_user_id;"
        ),
        
        migrations.RunSQL(
            "CREATE INDEX idx_post_status ON post(status);",
            reverse_sql="DROP INDEX idx_post_status;"
        ),
        
        migrations.RunSQL(
            "CREATE INDEX idx_post_created_at ON post(created_at DESC);",
            reverse_sql="DROP INDEX idx_post_created_at;"
        ),
    ]

Проверка существующих индексов

# SQL запрос для проверки всех индексов
SELECT 
    tablename,
    indexname,
    indexdef
FROM pg_indexes
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY tablename, indexname;

# Проверка неиспользуемых индексов
SELECT 
    schemaname,
    tablename,
    indexname,
    idx_scan,
    idx_tup_read,
    idx_tup_fetch
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY tablename;

Лучшие практики

1. Всегда добавляй индекс на внешний ключ

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)

2. Индексируй столбцы, используемые в WHERE

class Product(models.Model):
    status = models.CharField(max_length=20, db_index=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, db_index=True)

3. Используй composite индексы для часто используемых комбинаций

# SQL
CREATE INDEX idx_posts_user_status ON posts(user_id, status);

# Django
from django.db import models

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    status = models.CharField(max_length=20)
    
    class Meta:
        indexes = [
            models.Index(fields=['user', 'status'], name='idx_posts_user_status'),
        ]

4. Регулярно проверяй и удаляй неиспользуемые индексы

# Удаление неиспользуемого индекса
DROP INDEX idx_old_index;

Понимание того, когда PostgreSQL создаёт индексы автоматически, критически важно для создания оптимальных и предсказуемых схем баз данных.