Pull to refresh

Comments 2

Это очень плохо и самое ужасное это работа с БД.
Каждый запрос в БД это упрощённо
* отправка запроса по протоколу БД
* парсинг запроса, синтаксический разбор
* поиск запроса в кэше запросов
* если нет, то составление плана запроса
* выделение ресурсов на выполнение запроса
* поиск данных в буферном кэше
* чтение отсутствующих с диска
* отправка запроса по протоколу БД
+ Постгре версионник значит на транзакцию идёт снапшот и чем быстрее завершим её, тем меньше ресурсов потребуется при конкурентной работе

На примере: createDept
C Go не работал, поэтому буду писать на псевдо языке и с PostrgeSQL тоже мало работал, поэтому могут быть синтаксические ошибки, но общий смысл будет понятен
    { // Проверим, существует ли строка по натуральному уникальному ключу UK
        var foo int
        exists, myerr := s.db.Get(reqID, tx, "DeptExists", &foo, in.Deptno)
        if myerr != nil {
            return myerr
        }
        if exists {
            return myerror.New("4004", "Error create - row already exists: reqID, Deptno", reqID, in.Deptno).PrintfInfo()
        }
    } // Проверим, существует ли строка по натуральному уникальному ключу UK

    { // Выполняем вставку и получим значение суррогатного PK
        rows, myerr := s.db.Exec(reqID, tx, "CreateDept", in)
        if myerr != nil {
            return myerr
        }
        // проверим количество обработанных строк
        if rows != 1 {
            return myerror.New("4004", "Error create: reqID, Deptno, rows", reqID, in.Deptno, rows).PrintfInfo()
        }

        // считаем созданный объект - в БД могли быть триггера, которые меняли данные
        // запрос делаем по UK, так как суррогатный PK мы еще не знаем
        exists, myerr := s.db.Get(reqID, tx, "GetDeptUK", newDept, in.Deptno)
        if myerr != nil {
            return myerr
        }
        if !exists {
            return myerror.New("4004", "Row does not exists after creating: reqID, Deptno", reqID, in.Deptno).PrintfInfo()
        }

т.е. сначала выполнится запрос «SELECT 1 FROM dept WHERE deptno = $1»
если он не вернёт данные, то вставим
INSERT INTO dept (deptno, dname, loc) VALUES (:deptno, :dname, :loc)
проверим кол-во строк, а потом запросим по уникальности вставленный IDшник

сделаем лучше
{ // Выполняем ВСЁ: вставку, проверку и получим значение суррогатного PK
    rows, myerr := s.db.Prepare("
        INSERT INTO dept (deptno, dname, loc)
        VALUES (:deptno, :dname, :loc)
        ON CONFLICT (dept) DO NOTHING
        RETURNING id;
    ")
    .QueryRow(in.deptno, in.dname, in.loc)
    .Scan(&new_dept_id)
    if rows != 1 {
        return myerror.New("4004", "Error create - row already exists: reqID, Deptno", reqID, in.Deptno).PrintfInfo()
    }
}

идём дальше
    { // Обработаем вложенные объекты в рамках текущей транзакции
        if in.Emps != nil {
            for _, newEmp := range in.Emps {
                // Копируем суррогатный PK во внешний ключ вложенного объекта
                newEmp.Deptno = null.Int{sql.NullInt64{int64(newDept.Deptno), true}}

                // создаем вложенные объекты
                if myerr = s.createEmp(ctx, tx, newEmp, nil); myerr != nil {
                    return myerr
                }
            }
        }
    } // Обработаем вложенные объекты в рамках текущей транзакции

т.е. в цикле начинаем выполнять createEmp, который не описан, но я так понимаю тоже предполагается что там выполняются все этапы: проверка, вставка, повторное извлечение. Если у нас 10 дочерних записей, то будет выполнено 30 запросов, хотя раз у нас есть json, просто в тексте, то его легко скормить Постгресу, он ещё оч круто оптимизирован на работу с JSON'ом
Запрос будт что-то вида:
-- in_json = '[{"empno":"empno1", "ename":"ename1", "job":"job1"....},{"empno":"empno2", "ename":"ename2", "job":"job2"....}....]'
-- new_dept_id

INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
WITH d AS (
  SELECT json_array_elements(:in_json::json) AS r
)
SELECT r->'empno', r->'ename', r->'job', r->'mgr', r->'hiredate', r->'sal', r->'comm', :deptno FROM d

т.е. суммарно заменили 30 запросов к БД 1м запросом, ещё на ходу и JSON распарсили, и кучу всяких не нужных структур из кода можно выкинуть и памяти тонну освободить…

ну и в конце у Вас идёт getDept который двумя запросами возвращает мастера и детеил
ну тут опять можно соптимизить, получить всё одним запросом, да ещё и сразу в готовом JSON'е который тут же и вернуть без всякого дополнительного оверхэда

Дак о чём это я… Я простейшими модификациями, базовым SQL-ем сократил кол-во кода, уменьшил кол-во запросов с условных ~35 до 4х, ну и работать это будет раз в 10-50 быстрее. Это ещё без всяких специфичных для постгреса оптимизаций, хотя он вроде всё это умеет:
строка->JSON->multi_table_insert + returning array->select->agregate_JSON
и всё это в одном запросе! но это уже тонкий тюнинг

В общем напишите 1 такой сервис, выкиньте весь тот оверхэд с типами, надстройками, моделями, убедитесь что всё это будет работать в десятки раз быстрее на более простом железе.
Вот кста хорошая лекция о Постгре, тут немного про другое, про то как хорошо они заточили работу с JSON, но опять же на более простом железе показывают лучшую производительность
www.youtube.com/watch?v=SNzOZKvFZ68

Далее про кэширование, тут я не на 100% уверен, но тоже видится, что команда постгреса над которым работают десятки и сотни человек и не один десяток лет, наверняка лучше справились с этой задачей. Не надо изобретать велосипед, лучше настроить и потюнить инструмент, который специально для этого предназначен

Спасибо, что очень внимательно прочитали статью. Ваш отзыв очень полезен.


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


Если ваше решение работает только с PostgreSQL и вам не нужна реализация проверок на слое backend, то в шаблоне нужно правильно реализовать слой DB.
Используйте максимально все возможности PostgreSQL. Библиотека jackc/pgx отлично поддерживает массовую вставку из срезов.


Очень может быть, что вам и Go не нужен — используйте стандартные возможности PostgreSQL.

Sign up to leave a comment.

Articles