---
title: "Базы, коллекции и документы"
description: "Client, Database и Collection в mongo-driver v2: что живёт на каждом уровне, ленивое создание коллекций, документы mongodb и их ограничения — с примерами."
course: "MongoDB в Go: официальный драйвер mongo-driver v2"
url: "https://golangmaster.ru/courses/mongo-driver/lessons/03-kollekcii-i-dokumenty/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 16
---

# Базы, коллекции и документы

*Курс «MongoDB в Go: официальный драйвер mongo-driver v2», урок 3 из 25. ~16 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/mongo-driver/lessons/03-kollekcii-i-dokumenty/*

В mongo-driver вся работа раскладывается на три объекта — Client, Database и Collection: между ними распределяются базы, коллекции и документы mongodb. Урок про то, что живёт на каждом уровне, почему коллекции создаются лениво, какими правилами ограничены имена и почему один клиент на процесс — почти всегда правильное число.

Сервер MongoDB выглядит изнутри как аккуратная матрёшка: инстанс содержит базы данных, база — коллекции, коллекция — документы. В SQL-мире вы привыкли, что таблицы нужно создавать командой и миграцией, а в документном мире половина этой церемонии исчезает — но взамен появляются правила, о которых молчат туториалы: ленивое создание, ограничения имён, лимит размера документа. Драйвер отражает ту же матрёшку типами: *mongo.Client, *mongo.Database, *mongo.Collection. Понимание того, что каждый из этих типов делает — а что только изображает, — отличает код, который случайно работает, от кода, который работает предсказуемо. В этом уроке пройдём все три уровня сверху вниз, потрогаем документы как единицу хранения и зафиксируем правила, которые спасут от коллекций с опечатками в имени. Всё это — на коде, который можно запустить локально сразу после урока: разница между «прочитал» и «потрогал» здесь особенно заметна, потому что драйвер поощряет эксперименты.

### Client, Database, Collection: три уровня драйвера

Вопрос «mongo коллекции go-код как достаёт» решается двумя вызовами. client.Database("shop") возвращает *mongo.Database — дескриптор базы, db.Collection("orders") возвращает *mongo.Collection — дескриптор коллекции. Важнейший факт про оба типа: это лёгкие дескрипторы, а не сетевые объекты. Ни Database, ни Collection ничего не отправляют на сервер в момент создания — они просто запоминают имя; создать их можно сколько угодно, даже в цикле, без единого пакета по сети. Вся сетевая жизнь начинается на операциях: InsertOne, FindOne, CountDocuments — вот где ходят пакеты. Оба типа потокобезопасны: один дескриптор коллекции можно смело разделять между горутинами, мьютексы в вашем коде для этого не нужны. Дешевизна дескрипторов даёт рабочий стиль: дескрипторы коллекций создаются в одном месте — в конструкторе хранилища или пакете моделей — и живут столько же, сколько приложение; никаких Collection-на-запрос. Два вызова Database с одним именем вернут два независимых дескриптора — они равнозначны, и с какими работать — вопрос стиля: половина кодовых баз берёт дескриптор базы один раз в конструкторе, а коллекции от него при инициализации хранилища.

### Дескрипторы в действии

```go
client, err := mongo.Connect(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
        log.Fatal(err)
}
defer client.Disconnect(context.Background())

db := client.Database("shop")
orders := db.Collection("orders")
users := client.Database("shop").Collection("users")

n, err := orders.EstimatedDocumentCount(context.Background())
if err != nil {
        log.Fatal(err)
}
fmt.Printf("заказов примерно: %d
", n)
```

Два уровня иерархии в четырёх строках: client.Database возвращает *mongo.Database, .Collection — *mongo.Collection. Ни один из этих вызовов не обращается к серверу — это дескрипторы-имена. EstimatedDocumentCount читает метаданные коллекции и отвечает быстро, но без гарантии точности; для точного счётчика с условиями существует CountDocuments с фильтром — о нём в уроке про фильтры.

### Документы mongodb: BSON, поля и _id

Документы mongodb — единица хранения и обмена: именно они летят по сети в BSON, именно их видит mongosh. Документ — упорядоченный набор полей: в BSON порядок значим, и драйвер это уважает, когда пишет структуры и bson.D. Типы полей богаче JSON: int32 и int64 различаются, дата — time.Time с миллисекундной точностью, деньги — Decimal128, идентификатор — ObjectID, значения могут быть массивами и вложенными документами без ограничения глубины, кроме лимита размера. Есть и привычные по JSON штуки: null, бинарные данные, логические значения. У каждого документа есть _id — первичный ключ коллекции: он существует всегда, уникален и неизменяем. Если вы его не задали, драйвер сгенерирует ObjectID на стороне клиента при вставке — об этом подробнее в уроке про [ObjectID](/courses/mongo-driver/lessons/12-objectid/). По умолчанию в коллекции нет требования совпадения форм: соседние документы могут отличаться полями, и это осознанная свобода — схема переезжает в ваши Go-структуры с bson-тегами, а база остаётся исполнителем. Поле _id после вставки неизменяемо — обновить его не даст ни драйвер, ни сервер, поэтому продумайте ключ заранее: миграция идентификаторов на живой коллекции делается перезаписью всех документов.

> **Лимит документа — 16 МБ.** Документ не может весить больше 16 мегабайт — ограничение на уровне сервера, и оно защитит вас от себя: полноразмерные видео в документ не запихнуть. Медиа хранят на диске, в S3 или GridFS, а в документе оставляют ссылки и метаданные.

### Коллекции создаются лениво

В MongoDB нет CREATE TABLE: база и коллекция возникают в момент первой записи. Вставили документ в shop.orders — появились и база shop, и коллекция orders, с дефолтными настройками. До первой вставки пустые коллекции не существуют — show collections в mongosh их не покажет. Ленивость удобна для прототипов и раздражает в проде, и вот почему: коллекция создаётся по имени, а имя берётся из кода, где живут опечатки. Плюс драйвера в том, что дескриптор с несуществующим именем ошибки не даст — минус в том, что ошибки и не даст: вставка молча создаст коллекцию с кривым именем, и половина данных окажется в order вместо orders. Для настроек — capped-коллекции фиксированного размера для кольцевых логов, валидация схемы, индексы — существуют явные команды создания, их разберём во второй половине курса — в уроках про индексы и валидацию схемы, а типовая вставка обходит их стороной.

> **Опечатка в имени коллекции — тихая катастрофа.** db.Collection("orderS") компилируется, вставляется и не ругается — вы просто получите вторую пустую коллекцию рядом с настоящей, а отчёты пойдут вразнос. Лечится скучно и надёжно: имена баз и коллекций объявляются константами в одном месте, а не строковыми литералами по всему коду. Туда же — обработка конфигурации: если имя коллекции приходит из переменной окружения, добавьте валидацию на старте, а не найдите её по кривым отчётам.

### Ленивое создание в живом виде

```go
// дескрипторы объявили — база ещё не существует
db := client.Database("shop")
orders := db.Collection("orders")

colls, _ := db.ListCollectionNames(ctx, bson.D{})
fmt.Println("коллекций:", len(colls)) // 0

// первая запись материализует базу и коллекцию
_, err := orders.InsertOne(ctx, bson.D{{Key: "sku", Value: "kb-87-tkl"}})
if err != nil {
        log.Fatal(err)
}

colls, _ = db.ListCollectionNames(ctx, bson.D{})
fmt.Println("коллекций:", len(colls)) // 1 — orders на месте
```

Живая демонстрация ленивого создания. ListCollectionNames читает список коллекций базы: до первой вставки он пуст, хотя дескриптор orders уже объявлен. InsertOne — глагол из следующего урока — создаёт и базу shop, и коллекцию orders одним пакетом. Повторите это локально рядом с mongosh: show dbs и show collections подтвердят каждый шаг.

### Capped-коллекции: кольцевой буфер в базе

Единственный распространённый сценарий, где коллекцию создают явно и заранее, — capped: фиксированный кольцевой буфер. db.CreateCollection(ctx, "events", options.CreateCollection().SetCapped(true).SetSizeInBytes(10_000_000)) создаёт коллекцию, в которой новые записи вытесняют старые, когда место кончилось: последние десять мегабайт событий всегда на месте, чистить ничего не нужно. Лимит по числу документов задаётся SetMaxDocuments. Типовые обитатели таких коллекций — аудиторские события, поток логов, очередь сообщений для чтения tailable-курсором — курсором, который не закрывается на конце, а ждёт новых записей, как tail -f. Альтернатива capped для чистки — TTL-индекс, который удаляет документы по возрасту: он требует отдельного индекса и разбирается в соответствующем уроке. Те же опции CreateCollection умеют навешивать валидатор схемы — до него дойдём отдельным уроком; для обычных коллекций дефолтного ленивого создания достаточно. Проверить настройки можно в mongosh через db.events.isCapped() — маленькая команда, которая иногда отвечает на большой вопрос «почему мои старые данные исчезли».

### Кто есть кто в иерархии

| Уровень | Аналог в SQL | Тип в драйвере | Что умеет |
| --- | --- | --- | --- |
| Client | — (соединение) | *mongo.Client | пул соединений, Ping, сессии, старт транзакций |
| Database | база данных | *mongo.Database | дескрипторы коллекций, команды уровня базы |
| Collection | таблица | *mongo.Collection | CRUD, индексы, агрегации, счётчики |
| Document | строка | структура, bson.M, bson.D | данные; лимит 16 МБ, обязателен _id |

Таблица подсказывает и ограничения, которые стоит знать до первого code review. Имя базы: от одного до 63 символов, чувствительно к регистру, без пробелов, точек, слэшей и знака доллара. Имя коллекции: не пустое, без знака доллара, не начинается с system. — этот префикс зарезервирован сервером; вместе с именем базы полное пространство имён не должно превышать 255 байт. Регистр важен: orders и Orders — две разные коллекции, и Windows-хостинги с нечувствительной файловой системой превращают это в отдельный вид боли, если вы решите переносить данные руками. Имена с автогенерированными сборками вроде tmp.orders_copy_2024_03_12 — признак того, что пора заводить константы и миграции.

### Правила имён одной строкой

- База: 1-63 символа, без пробела, точки, слэша и $; регистр значим.
- Коллекция: без $, не пустая, не начинается с system.; регистр значим.
- Пространство имён db.coll — не больше 255 байт.
- orders и Orders — разные коллекции; проверяйте регистр в конфигах.
- Временные имена вида copy_2024_03_12 — запах ручных операций.

### Один клиент на процесс

Сколько нужно клиентов — вопрос с почти всегда одним ответом: один. *mongo.Client потокобезопасен и ведёт внутренний пул соединений: горутины берут сокеты по мере надобности и возвращают их обратно, а вам не нужно ничего синхронизировать. Дескрипторы Database и Collection создаются от клиента в один момент — обычно в конструкторе приложения — и передаются зависимостям дальше: репозиториям, хендлерам, воркерам. Второй клиент оправдан в двух случаях: тесты с отдельным окружением и редкие операции, которым нужна изоляция настроек, вроде отдельного read preference для аналитики; оба сценария мы тронем позже — в уроках про тесты и производительность. Клиент на каждый HTTP-запрос — антипаттерн из первого дня: каждый несёт собственный пул, сервер копит лишние сокеты, а latency растёт на установке соединения. Если мигрируете с ORM-мира, ориентир тот же, что и у *sql.DB или [GORM](/courses/gorm/): долгоживущий хендл, короткоживущие операции. Отдельный плюс одного клиента — единая точка настройки: параметры пула, read preference по умолчанию и мониторинг настраиваются на клиенте один раз, а не в каждой коллекции.

Мультитенантность — сценарий, где вопрос «одна база или несколько» возникает всерьёз. Три рабочих раскладки. Первая: общая база, поле tenant_id в каждом документе и индекс, начинающийся с него; проще всего в эксплуатации, требует дисциплины в фильтрах. Вторая: общая база, коллекция на клиента (orders_acme, orders_globex); изоляция данных нагляднее, но количество коллекций растёт линейно с клиентами, а кросс-тенантные отчёты превращаются в обход. Третья: база на клиента; максимальная изоляция — бэкапы и экспорт клиента делаются командой, но лимиты на количество коллекций и администрирование наступают быстрее, чем кажется. Я вижу первую раскладку по умолчанию и откат к третьей, когда у клиента есть требование физической изоляции. Выбирайте до того, как появятся данные: переезд между раскладками — это ручная миграция каждой коллекции. И не переусложняйте раньше времени: если клиентов меньше пяти, первая раскладка выигрывает почти всегда, а изоляция добирается дисциплиной фильтров и индексом с префиксом tenant_id.

### Проверка: ленивое создание

**Вопрос:** Что произойдёт при первой вставке в несуществующую коллекцию?

- Ошибка: коллекцию надо создать заранее
- ✅ Драйвер создаст коллекцию автоматически
- Ошибка только в кластере без репликасета
- Документ вернётся с флагом discarded

Базы и коллекции создаются лениво при первой записи, с настройками по умолчанию. Обратная сторона: опечатка в имени не выдаст себя — вставка молча родит новую пустую коллекцию, поэтому имена держат в константах.

### Проверка: сколько клиентов

**Вопрос:** Когда оправдан второй экземпляр *mongo.Client?

- На каждый HTTP-запрос — для изоляции
- На каждую коллекцию — для скорости
- ✅ Тестовое окружение или изоляция настроек вроде read preference
- Никогда: клиент существует в единственном экземпляре

Клиент один на процесс: он потокобезопасен и держит пул. Второй оправдан для тестов с отдельным окружением или для специализированного доступа с другими настройками, например аналитики с read preference secondary.

### Какой порядок полей уйдёт в BSON?

```go
doc := bson.D{
        {Key: "type", Value: "стол"},
        {Key: "price", Value: 100},
}

data, err := bson.MarshalExtJSON(doc, false, false)
fmt.Println(string(data), err)
```

**Что выведет программа?**

- ✅ {"type":"стол","price":100} — в порядке объявления
- {"price":100,"type":"стол"} — по алфавиту
- порядок случайный при каждом запуске
- ошибка: поля нельзя смешивать

bson.D — упорядоченный документ: срез пар в том порядке, как вы их задали, и BSON сохранит именно его. Порядок полей в BSON значим — он влияет на индексы и валидацию. Для беспорядка есть bson.M, но команды сервера любят упорядоченность.

### Соберите дескрипторы

Допишите два уровня иерархии и быстрый счётчик документов.

```go
db := client.___("shop")

orders := db.___("orders")

n, err := orders.___(context.Background())
if err != nil {
        log.Fatal(err)
}

fmt.Println("заказов примерно:", n)
```

Подсказка: client.Database, db.Collection, счётчик по метаданным — EstimatedDocumentCount.

Правильные ответы в пропусках: `Database`, `Collection`, `EstimatedDocumentCount`

### Соберите работу с дескрипторами

Расставьте строки в порядке выполнения: клиент, база, коллекция, счётчик, вывод, отключение.

Правильный порядок строк:

1. `opts := options.Client().ApplyURI("mongodb://localhost:27017")`
2. `client, err := mongo.Connect(opts)`
3. `db := client.Database("shop")`
4. `orders := db.Collection("orders")`
5. `n, err := orders.EstimatedDocumentCount(context.Background())`
6. `fmt.Println("документов:", n)`
7. `err = client.Disconnect(context.Background())`

### Карточки: иерархия драйвера

- ***mongo.Client** — Корень всего: пул соединений, Ping, сессии, транзакции. Потокобезопасен, один на процесс.
- ***mongo.Database** — Дескриптор базы: выдаёт коллекции и выполняет команды уровня базы. Создаётся client.Database("shop"), сеть не трогает.
- ***mongo.Collection** — Дескриптор коллекции: CRUD, индексы, агрегации. Лёгкий и потокобезопасный, живёт столько же, сколько приложение.
- **Ленивое создание** — База и коллекция появляются при первой вставке. Опечатка в имени не даст ошибки — молча создаст новую коллекцию.
- **Лимит 16 МБ** — Максимальный размер документа на уровне сервера. Медиа — на диск или GridFS, в документе ссылки.
- **bson.D** — Упорядоченный документ: срез пар поле-значение в заданном порядке. Порядок в BSON значим.

Иерархия собрана: клиент с пулом, дескрипторы баз и коллекций без сетевой цены, документы с обязательным _id и лимитом 16 МБ, имена — в константах, второй клиент — только для тестов. Дальше логично сделать первый шаг записи: в уроке про [InsertOne и InsertMany](/courses/mongo-driver/lessons/04-insert-dokumentov/) вставим документы и разберёмся с автогенерацией _id и порядком вставки. Потом прочитаем их обратно в уроке про [FindOne](/courses/mongo-driver/lessons/06-find-one/) и разберём ErrNoDocuments. Отдельный совет для практики: дескрипторы и ленивое создание проверяются только на живом сервере — песочница сайта показывает тематическую программу на чистом Go, поэтому поднимите контейнер из второго урока и посмотрите глазами, как после первого InsertOne появятся база и коллекция: эта визуализация стоит половины теории. Пока учитесь, держите mongosh открытым постоянно: show dbs и show collections после каждого нового дескриптора мгновенно показывают, что и где создалось, а MongoDB Compass — бесплатный GUI от тех же разработчиков — открывает документы, индексы и планы запросов без консоли. И последний мостик вперёд: когда дойдём до чтения, окажется, что дескрипторы коллекций — ещё и точка настройки курсоров, проекций и агрегаций; поэтому имена и структура хранилища, выстроенные сейчас, останутся с вами до конца проекта.

## Коротко: что запомнить

- Иерархия: Client — пул соединений, Database и Collection — лёгкие дескрипторы-имена без сетевых операций.
- Дескрипторы потокобезопасны и дёшевы: создаются один раз и передаются зависимостям.
- Документы mongodb: BSON-поля с типами, обязательный _id, лимит 16 МБ, порядок полей значим.
- Базы и коллекции создаются лениво при первой записи — опечатка в имени молча плодит новые коллекции.
- Имена: база до 63 символов без пробелов и $; коллекция без $, не с system., регистр значим.
- Один клиент на процесс; второй — только тесты или изолированные настройки доступа.
- Запуск — локально с MongoDB из Docker; mongosh и Compass показывают, что создалось.

## Домашнее задание

Поднимите MongoDB из второго урока и напишите программу: подключитесь, возьмите дескрипторы client.Database("shop") и db.Collection("orders"), вставьте один документ через coll.InsertOne (сигнатура подглядывается в следующем уроке), а после этого выполните в mongosh show dbs и show collections и найдите созданную базу. Затем переименуйте коллекцию в коде в "orderS", повторите вставку и убедитесь, что появилась вторая коллекция — цена опечатки в имени.

## Вопросы и ответы по уроку

### Как создать коллекцию явно, без вставки?

Методом coll := db.CreateCollection(ctx, "orders") с опциями вроде capped или validator. Обычные коллекции создавать заранее не нужно — они появятся при первой вставке; явное создание оправдано только для настроек.

### Можно ли переименовать коллекцию?

Да, командой renameCollection на уровне базы, но она блокирует коллекцию и не работает между базами без оговорок на шардинге. Поэтому в коде имена коллекций держат в константах: переименование на живых данных — операция, а не правка строки.

### Что будет, если в одну коллекцию вставить документы с разными полями?

Ничего страшного: без валидатора схема коллекции не навязывается, документы сосуществуют. Контракт формы в этом случае несёт ваш код — Go-структуры с bson-тегами; формализовать его можно JSON Schema-валидатором коллекции, этому посвящён отдельный урок курса.
