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

Ответ

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

Основные характеристики

  • Жизненный цикл: Существует только в рамках текущего соединения (сеанса) или транзакции.
  • Видимость: Видна только тому соединению, которое её создало. Другие сеансы не могут получить к ней доступ, что исключает конфликты имен с таблицами в других сеансах.
  • Производительность: Операции с временными таблицами могут быть быстрее, так как для них часто используется менее строгое журналирование (logging).
  • Хранение: Обычно хранятся в специальной области (temporary tablespace) или в оперативной памяти.

Сценарии использования

Временные таблицы незаменимы для хранения промежуточных результатов в сложных многоэтапных запросах:

  1. Декомпозиция сложных JOIN'ов: Вместо одного огромного запроса с множеством соединений можно выбрать данные в несколько временных таблиц и затем соединить их.
  2. Генерация отчетов: Собрать данные из разных источников в одну временную таблицу для последующей агрегации и анализа.
  3. Массовые операции: Сохранить список идентификаторов (ID) во временную таблицу для последующего использования в UPDATE или DELETE запросах.

Пример на SQL (PostgreSQL/MySQL)

Синтаксис может незначительно отличаться в разных СУБД (например, в MS SQL Server используются таблицы с префиксом #).

-- Создание временной таблицы, которая будет удалена в конце сессии
CREATE TEMPORARY TABLE temp_users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    is_active BOOLEAN
);

-- Наполнение и использование таблицы
INSERT INTO temp_users (name, is_active) VALUES ('Alice', true), ('Bob', false);

-- Выборка активных пользователей из временной таблицы
SELECT * FROM temp_users WHERE is_active = true;

-- После закрытия соединения с БД таблица 'temp_users' будет автоматически удалена.

Работа с временными таблицами в Go

В Go нет специального API для временных таблиц. Работа с ними ведется через стандартные SQL-запросы с помощью пакета database/sql. Однако есть важный нюанс, связанный с пулом соединений.

Ключевой момент: Временная таблица привязана к конкретному соединению. Пакет database/sql управляет пулом соединений, и каждый вызов db.Exec() или db.Query() может использовать разное соединение из пула. Поэтому, чтобы гарантированно работать с временной таблицей, все операции (создание, наполнение, чтение) нужно выполнять в рамках одной транзакции или одного соединения.

Правильный подход — использование транзакций:

package main

import (
    "database/sql"
    "log"

    _ "github.com/lib/pq" // Драйвер для PostgreSQL
)

func main() {
    db, err := sql.Open("postgres", "user=... password=... dbname=... sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Начинаем транзакцию. Все последующие команды до Commit/Rollback
    // будут выполнены в рамках одного соединения.
    tx, err := db.Begin()
    if err != nil {
        log.Fatal(err)
    }
    // Гарантируем откат транзакции в случае ошибки
    defer tx.Rollback()

    // 1. Создаем временную таблицу
    _, err = tx.Exec(`CREATE TEMPORARY TABLE temp_products (id INT, name TEXT)`)
    if err != nil {
        log.Fatal("Failed to create temp table: ", err)
    }

    // 2. Наполняем ее данными
    _, err = tx.Exec(`INSERT INTO temp_products (id, name) VALUES ($1, $2)`, 1, "Laptop")
    if err != nil {
        log.Fatal("Failed to insert into temp table: ", err)
    }

    // 3. Читаем данные из нее
    var productName string
    err = tx.QueryRow(`SELECT name FROM temp_products WHERE id = $1`, 1).Scan(&productName)
    if err != nil {
        log.Fatal("Failed to query temp table: ", err)
    }

    log.Printf("Product from temp table: %s", productName)

    // Завершаем транзакцию. После этого временная таблица может быть удалена
    // (зависит от настроек СУБД, но в конце сессии она исчезнет точно).
    if err := tx.Commit(); err != nil {
        log.Fatal(err)
    }
}

Ответ 18+ 🔞

А, ну это ж классика, блядь! Временные таблицы в базах данных — это как одноразовый стаканчик на вечеринке: налил, выпил, выкинул в пизду, и никто не помнит, что ты там бухал. Суть в том, что эта штука живёт ровно до тех пор, пока ты не отключишься от базы или не завершишь транзакцию. Потом — хуй, испарилась, как твои надежды на повышение зарплаты.

Что это за зверь такой:

  • Живёт недолго: Создал в рамках своей сессии или транзакции — и всё, блядь. Закрыл коннект — таблица накрылась медным тазом. Не лезет в постоянную схему, не мусорит.
  • Видит только ты: Другие подключения к этой же базе её не увидят. Идеально, чтобы не пересекаться именами с каким-нибудь коллегой-распиздяем, который тоже назвал свою таблицу temp_shit.
  • Быстрая, потому что похуистичная: Часто под неё журналирование (это когда всё логируют) слабее, поэтому операции могут летать. Может храниться в оперативке или в специальном временном пространстве — не в постоянных файлах, которые жрут место.

Зачем это надо, ёпта?

Ну, например, когда у тебя запрос такой ебученно сложный, что проще разбить его на части. Вместо одного монстра с десятью JOIN, который даже СУБД плачет, можно:

  1. Выгрести первую пачку данных во временную таблицу.
  2. Присобачить к ней вторую.
  3. Всё это проагрегировать и получить отчёт. Или, допустим, нужно сделать массовое обновление — выбрал IDшники во временную табличку, а потом по ним UPDATE или DELETE запулил. Удобно, блядь!

Смотри, как в SQL это выглядит (например, в PostgreSQL):

-- Создаём таблицу-призрак. Умрёт, когда сессия кончится.
CREATE TEMPORARY TABLE temp_orders (
    id SERIAL PRIMARY KEY,
    user_id INT,
    total DECIMAL
);

-- Пихаем в неё данные
INSERT INTO temp_orders (user_id, total) VALUES (42, 999.99), (777, 0.01);

-- Читаем их, как будто так и надо
SELECT * FROM temp_orders WHERE total > 100;

-- И всё... Закрыл коннект — и таблицы нет, будто и не было. Магия, сука!

А теперь главное, про Go, где всё всегда весело!

В Go нет волшебной палочки для временных таблиц. Работаешь через обычные SQL-запросы. Но есть одна хитрая жопа, о которую все спотыкаются: пул соединений.

Библиотека database/sql держит кучу коннектов к базе. И когда ты делаешь db.Exec(), она может каждый раз выдавать тебе разное соединение из пула. А временная таблица привязана к конкретному соединению. Представь: ты создал таблицу в одном соединении, а в следующем запросе пытаешься в неё записать — а тебе выдают другое соединение, где этой таблицы, блядь, НЕТУ! И получаешь ошибку «relation does not exist». Удивление пиздец!

Как правильно? Держать всё в одной транзакции! Транзакция гарантированно использует одно соединение.

package main

import (
    "database/sql"
    "log"
    _ "github.com/lib/pq"
)

func main() {
    db, err := sql.Open("postgres", "user=... password=... dbname=...")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Начинаем транзакцию — это наш личный, непередаваемый коннект.
    tx, err := db.Begin()
    if err != nil {
        log.Fatal(err)
    }
    defer tx.Rollback() // На всякий пожарный, если что-то пойдёт не так.

    // 1. Создаём временную таблицу В ЭТОЙ транзакции.
    _, err = tx.Exec(`CREATE TEMPORARY TABLE temp_cart (item_id INT, qty INT)`)
    if err != nil {
        log.Fatal("Не создалась таблица, блядь: ", err)
    }

    // 2. Пихаем в неё что-нибудь.
    _, err = tx.Exec(`INSERT INTO temp_cart VALUES ($1, $2)`, 555, 3)
    if err != nil {
        log.Fatal("Не вставилось, ёпта: ", err)
    }

    // 3. Читаем обратно. Всё в рамках той же транзакции — значит, та же таблица.
    var quantity int
    err = tx.QueryRow(`SELECT qty FROM temp_cart WHERE item_id = $1`, 555).Scan(&quantity)
    if err != nil {
        log.Fatal("Не прочиталось, сука: ", err)
    }
    log.Printf("Достали из временной таблицы: %d штук", quantity)

    // Фиксируем. После коммита транзакция завершена, но сессия-то ещё жива.
    // Таблица будет висеть, пока это соединение не вернётся в пул и не закроется.
    // Но обычно на это забивают — главное, что в рамках логики всё работало.
    if err := tx.Commit(); err != nil {
        log.Fatal(err)
    }
    // А вот когда эта функция завершится и db.Close() закроет все коннекты,
    // тогда временная таблица точно отправится в цифровое небытие.
}

Вот и весь секрет, блядь. Создал в транзакции — используй в транзакции — удалится сама. Красота, ёперный театр!