Базы данных в HighLoad. Советы и рекомендации по работе с БД в крупных проектах

Поделиться
HTML-код
  • Опубликовано: 23 ноя 2024

Комментарии • 179

  • @wamikgildiev6632
    @wamikgildiev6632 15 дней назад

    Комментарии везде и это удобно....Просто браво....Особенно это удобно в полях типа fio, name, surname, middlename, age, status и тд....Действительно, если бы не было комментариев, хер бы кто догадался что это такое!!!

  • @alexanderspeshilov839
    @alexanderspeshilov839 3 месяца назад +5

    Мои пять копеек:
    - Присоединяюсь к тому, что uuid желательно брать time-based. Желательно, чтобы его поставщиком был не сервер СУБД, а приложение (сервер приложений, конечно же).
    - Для статических классификаторов и "enum" таблиц иногда допустимы естественные ключи. Но не числовые, а текстовые. Часто это короткий код. Но надо понимать, что замена этих кодов сразу превращается в жирный эпик. Но если, например, эти значения уже используются в коде, то так и так попадаем на редактирование кода и тогда это не важно.
    - Про "вторичные" ключи тоже резануло слух. Да, убрать FK - стандартная практика. Да, это приводит к возможности "битых" ссылок, но это часто не бага, а фича.
    - Строки. Строки должны быть в Unicode (для pg зто по умолчанию, но для MS SQL - надо явно это фиксировать). Однобайтные (не unicode) кодировки - в бан.
    - Числа. float/double только для хранения информации в физическом моделировании. Цены, суммы, количество - не должны быть double. Если у вас приложение про деньги, то основной тип - с десятичной точкой (зависит от СУБД). Arbitrary precision типы не должны использоваться (ну кроме крайне редких случаев). Обычные целочисленные типы - норм, но не про экономические значения (там лучше использовать с десятичной точкой даже для целочисленных)
    - JSONB, XML и подобные. Не увлекайтесь. Да, это оперативно-тактически позволяет экономить на проектировании схемы, но в долгосрочной работе обычно приводит к сложным проблемам.
    - Хранение бинарников (blob) в базе. Обычно - нет. Если бинарные данные больше ~100 килобайт - точно нет. В первую очередь потому что WAL. Кстати - понимание WAL, понимание уровней изоляции и транзакционных механизмов обязательно для всех разработчиков БД (если кандидат будет знать транзакции, изоляцию и WAL, но не знать про индексы - я его возьму вероятнее, чем наоборот).
    - Но при этом всё необходимое для работы приложения обычно должно быть в одной БД. В том числе настройки (кроме креденшелов, адресов и путей, конечно). Некоторые и код приложения кладут в БД, но это кому-то подходит, кому-то не очень.
    - Разделите OLTP и аналитическую нагрузку. Хотя бы внутри одной БД (в идеале, на разные сервера, на разные сервисы с разными моделями данных и т.п.).
    - Пишущие транзакции должны начинаться и заканчиваться на стороне СУБД без передачи управления серверу приложений. И уж тем более не должно происходить ожидание внешних событий (реакции пользователя, http-запроса) при открытой транзакции
    - В большинстве крупных внедрений с СУБД работают только технические учетные записи. У конечного пользователя не должно быть отдельного логина или роли в СУБД, пусть этим сервер приложений голову греет.
    - RLS (row level security) следует использовать очень осторожно. Все RLS которые я видел способны легко убить производительность.
    - Не используйте схемы типа EAV (entity-attribute-value). Вы зае..тесь потом с запросами, целостностью, производительностью, объёмом данных и т.п. Просто не используйте. Ну разве что для настроечной таблицы, но и то - одумайтесь.
    - В базу должен ходить только ваш сервис. Другие системы (даже если они ETL и КХД) должны ходить через сервис. Структура базы - никакая её часть - не должны становиться контрактом сервиса.
    - Про триггеры в целом согласен, но с дополднением. Если уж надо что-то "типа триггеров", то посмотрите на материализованные представления. Но аккуратно.
    Нет, пункты не кончились, просто писать надоело :)

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  2 месяца назад

      Не знаю почему ютуб сразу не пропустил ваш комментарий, откопал одобрил, подписываюсь под каждым словом, прекрасно написано!

  • @MichioSempai
    @MichioSempai 3 месяца назад +2

    Вопросы. Большое спасибо за видео 1. Кто гененирует uuid записи, приложения или бд? 2. Кто дает гарантии что uuid будет уникальным или мы подагаемся на спецификацию (особенно актуально если нужно обьеденить данные из нескольких кластеров) 3. Как часто используюься сложные типы такие характерные для pg ( jsonb, hstore, array и т.д)

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      1. Приложение. 2. Сам UUID и дает гарантии, вероятность совпадения двух правильно сгенерированных UUID практически равна нулю (она настолько низка что тебе дадут премию если ты сгенерируешь коллизию. 3. Для хранения таких данных мы не используем PostgreSQL, тут лучше подойдет что-нибудь нереляционное вроде Redis

    • @RyAndrey
      @RyAndrey 2 месяца назад

      По первому я бы сказал база, но в редких случаях проще на стороне приложения сгенерировать и сразу в несколько других таблиц прокинуть uuid. Хотя, конечно, можно сначала запросить у базы, а потом уже прокидывать. Короч, зависит от задачи, но по дефолту на стороне базы

  • @ruslooob
    @ruslooob 3 месяца назад +2

    По поводу имен таблиц, есть еще мнение именовать в единственном числе, чтобы писать более читаемые запросы. Например select в таблице person будет выглядеть так.
    select person.id, person.name from person where id = ?;
    против
    select persons.id, persons.name from persons where id = ?;
    Пример с джоинами
    select order.*, person.id, person.name from order inner join person on order.person_id = person.id;
    против
    select order.*, persons.id, persons.name from order inner join persons on order.person_id = persons.id;
    Имхо, вариант с именованием таблиц в единственном числе в этом случае читаемее, потому что не приходится в голове делать преобразование из множественного числа в единственное во время написания запроса. Можно конечно поставить алиас во 2 варианте, но это больше букв)
    Спасибо за видео, ты первый, кто сказал про неиспользование Transactional & Hibernate-аннотаций. У меня на работе такое же мнение относительно этих штук.

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  2 месяца назад +2

      когда набирают запросы руками, в 99% случаев вижу запросы формата
      select p.id, p.name from persons p where id = ?;
      используют алиасы
      а в коде используем JPQL и там иначе все выглядит
      ```
      @Query(
      """
      SELECT transaction.id as id
      FROM TransactionPartitioned transaction
      WHERE transaction.utrnno = :utrnno
      """
      )
      fun findExistingPaymentTransactions(utrnno: String): List
      ```

  • @madcalm2024
    @madcalm2024 2 месяца назад +1

    Очень необычные, половинчатые советы. Например про UUIID id... Во первых, нет математической гарантии их неповторямости... и использовать UUID рационально только как дополнительное поле в качестве вторичного ID для связей между фрагментами распределенной БД... а для связей в локальных БД ясен перед должен использоваться быстрый и компактный INTEGER id... и самое главное для большой БД особенно на механических HDD - во избежание будущих тормозов на SELECT-запросах ни в коем случае нельзя создавать индексы по полям и функциям, которые участвуют в запросах и одинаковые значения которых равномерное распылены по всему объему записей - например по кодам операторов, по типам документов, по суммам платежей и т.д.

  • @ИльяГрин-й8т
    @ИльяГрин-й8т 3 месяца назад +9

    имба видос, столкнулся с такими же проблемами, давай еще про highload

  • @honey7460
    @honey7460 3 месяца назад +1

    Спасибо за видео, Кевин де Брейне

  • @bobhutchinson3638
    @bobhutchinson3638 3 месяца назад +9

    На моменте 10:35, с версии postgresql 11, добавление колонки с дефолт значением в существующую таблицу, не вызовет перезаписи значений по всех строках.

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +3

      спасибо за ремарку, не знал, там оказывается теперь какая-то хитрая логика с постепенным назначением дефолтных значений, начиная с 11 версии.

  • @Aleksey-n5h
    @Aleksey-n5h 2 месяца назад +1

    Если вы не используете increment, то что вы используете для справочных таблиц для pk, например "colors", "sizes", "types", "materials"? Естественный id или сгенерированный? Если естественный, то как вы называете? "id" или "name"

  • @Interoper
    @Interoper 3 месяца назад +2

    Итог "Чем проще - тем лучше"

  • @АртемИвлев-б3э
    @АртемИвлев-б3э 3 месяца назад

    Как вариант, можно приложения писать таким образом, чтобы при поиске строк не использовать первичный ключ, а другие уникальные поля с индексами, тогда не будет необходимости, чтобы один и тот же инстанс сущности имел одинаковые id в разных окружениях

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      Очень сложно избегать использования первичного ключа) это уже тогда смотреть в сторону альтернативных ключей, тут упоминали в комментах

  • @alexmylex8645
    @alexmylex8645 3 месяца назад +1

    Спасибо за советы. Может у вас есть подходы к нормализации-денормализации бд раз вы не используете вторичные ключи и полагаетесь на уровень приложения? Сам много лет придерживаюсь подобных правил по неймингу, служебным полям и связям между сущностями. Также интересно узнать как вы проводите оптимизация работы вашего приложения в контексте базы данных. Спасибо за ответ :)

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +2

      Денормализация строится в зависимости от бизнес процессов. Иногда, в угоду производительности выгодно дублировать данные в некоторых таблицах. Чаще всего мы добавляем элементы денормализации в виде ссылок между сущностями.
      Например: транзакция имеет ссылку на услугу, услуга имеет ссылку на договор, договор имеет ссылку на филиал.
      Бизнес процесс должен обработать все транзакции по филиалу Х.
      Вместо того чтобы в момент работы бизнес процесса тянуть все 3 сущности из базы (замечу, что в микросервисной архитектуре эти сущности принадлежат разным сервисам и могут лежать в разных базах), мы (в момент обработки транзакции) проставляем на транзакции идентификатор филиала, для упрощения работы другим бизнес процессам.

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +2

      Оптимизация базы данных через анализ отчетов pgbadger / анализ планов запросов / построение индексов / организацию работы с базой батчами

    • @alexmylex8645
      @alexmylex8645 3 месяца назад

      @@konstantin.kozlovskiy Спасибо за ответ. У себя на практике архитектурно использую подход Data vault в виде таблиц сущностей и словарей свойств плюс таблицы отношений, часто он приводит к денормализации, когда бизнес логика лежит в приложении.

  • @Aleksey-n5h
    @Aleksey-n5h 3 месяца назад

    Интересно услышать про текстовые поля. text / varchar / char. В каких случаях, какие использовать. Есть ли проблемы с ними

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      приведи примеры, использовать там где нужны строки, не использовать там где НЕ нужны строки, все просто ))
      что text что varchar для PostgreSQL разницы нет, в последних версиях рекомендуют даже размерность не указывать

  • @rediskavet
    @rediskavet 3 дня назад

    "Переливать данные между полигонами" типа скорпировать часть данных с прода на QA сервер?
    как это технически делают? Как принято делать в больших конорах?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 дня назад

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

    • @rediskavet
      @rediskavet 3 дня назад

      @ ну зависит от данных наверное, личные данные может и не заливают.
      Но рейты акций, почему нет?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 дня назад +1

      @ если генерация синтетических данных для наполнения тестового контура невозможна, прибегают к процедуре обезличивания, этим занимаются целые команды, базу загружают в специальное место где каждую колонку с данными проверяют и подвергают обезличиванию с сохранением бизнес целостности, т.е если в базе ИНН организации встречается в нескольких местах, его обезличивают везде одинакого. Это очень сложная и кропотливая процедура.

  • @andreii-driuk
    @andreii-driuk 3 месяца назад +5

    Шикарное видео

  • @X7899X
    @X7899X 3 месяца назад

    Убрали мы внешний индекс и для целостности данных нам нужно тогда сделать как минимум один select с limit 1 для проверки. Это оправданно к примеру когда данных на столько много что пересчет дерева занимает больше времени чем операция select. Но сразу несем потери из-за сетевых транзакций. Допустим две таблицы по 10 млн записей. И пришел к нам тот самый select перед вставкой. И так получилось, что нужное поле в самом конце. Postgres пробежит по всем строкам. Ок! Программист тогда понимает что это долго и берет индексирует поле. Ждет пол дня а потом радуется. Но тогда в чем был смысл вообще не ставить индексы в самом начале. На то они и реляционные базы данных что-бы иметь структуру данных. Есть другие базы типа Cassandra как раз для таких задач конечно там есть и свои нюансы. Потому всегда нужно выбирать компромис.

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      Внешний ключ проверяет целостность при любых манипуляциях с зависимыми данными. Если тебе нужно вставить 10млн записей в одну таблицу и 10 млн записей в другую, и они связаны. Без внешних ключей ты можешь делать это параллельно и с более высокой производительностью. Да есть риск - если у тебя в коде баг, возможно нарушение целостности. Но как я упоминал в видео, если ты связал две таблицы внешним ключом и решил их партиционировать (у нас же уже 10 млн и с каждым днем всё прибывает), то ничего не выйдет)

  • @toxesfoxes
    @toxesfoxes 3 месяца назад +1

    Видео топ, почему так мало лайков(

  • @First_NameSecond_Name
    @First_NameSecond_Name 3 месяца назад

    Он имеет ввиду ВНЕШНИЙ КЛЮЧ, когда говорит о вторичном ключе?

  • @kacetal
    @kacetal 3 месяца назад +1

    Вот только автор забыл сказать что uuid должен быть timebased uuid v7 иначе постгрес будет ребалансировать свое B-tree после каждой вставки, а лучше всего просто взять timebased long, что-то вроде snowflake ID который и место меньше займёт и выглядит нормально и работать будет быстрее классического uuid.

  • @instl1117
    @instl1117 3 месяца назад

    Привет! Интересное видео, возникло несколько вопросов:
    1. Почему вы используете реляционную бд, а не nosql?
    2. Какой либой вы получаете данные из бд?
    3. Если вы не используете @Transactional, то не боитесь ли грязного чтения?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +1

      1. Мы не используем Nosql потому что нам нужна Sql база данных, реляционная. Я не понимаю почему вообще может возникнуть такой вопрос, у нас не документы, у нас транзакции и зависимые сущности которые мы раскладываем по таблицам и собираем в разном объеме при необходимости. Нет у нас JSON документов которые нужно складывать по ID и доставать по ID.
      2. Мы используем Hibernate
      3. Если мы не используем аннотацию @Transactional это не значит что у нас произойдет грязное чтение, через фреймворк любые изменения данных аля update / delete все равно пойдут в транзакции, аннотация будет стоять но на самом низко-уровневом методе репозитория а не где-то в слое бизнес логики. Грязных чтений не будет.

    • @MrAlexandrStv
      @MrAlexandrStv 2 месяца назад +1

      А если вам надо сделать операцию в БД, а потом логику и ещё раз операцию в БД. Если у вас транза в репо только то нет единой атомарности этих операций.

  • @Евгений-ы4м3ж
    @Евгений-ы4м3ж 2 месяца назад

    Если не ошибаюсь некоторые бд даже не позволяют создавать вторичные ключи

  • @vodchiy
    @vodchiy 3 месяца назад +1

    Со многим согласен, вопрос про внешние ключи. Мне не совсем понятен ваш концепт. Например есть таблицы users и transactions. Например в transactions уже 40 млн записей. В ней есть поле user_id, и когда я хочу получить последние 10 транзакций определённых пользователей я так или иначе буду их отсеивать по полю user_id, например соберу id нужных пользователей и буду проверять вхождение в этот массив. Ну и для этого я наброшу на поле user_id индекс, чтобы поиск был быстрым. И по сути это можно назвать внешним ключом. Точнее реализовать это можно как внешний ключ. Расскажите как вы реализуете подобные выборки, отдельно ли индекс создаёте в приложении?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      индексы конечно нужны, но если у тебя две транзакционных таблицы, например transactions & transaction_data, и между ними есть связь, обе таблицы огромные и тебе необходимо их партиционировать, то между ними foreign ключей быть не может, а индексы конечно быть должны. если рассматривать таблицы users & transactions то вероятно таблицу пользователей ты захочешь шардировать и тут foreign ключи тоже тебе помешают. Такие ключи не доставляют неудобств только между таблицами справочниками, в которых не часто происходит вставка и которые не нужно шардировать или партиционировать

  • @Дмитрий-ю9к3г
    @Дмитрий-ю9к3г 3 месяца назад +1

    А почему нейминги boolean по типу IS_active считаете не лучшим вариантом?

    • @Mytest437
      @Mytest437 3 месяца назад

      Тоже удивился и не понял. Наоборот такой стиль позволяет лучше видеть, что там находиться в значении.

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      на самом деле это вкусовщина, просто когда будете мапить эти поля в java, геттеры будут генерироваться isActive(), а для других полей getXXX(), мне не нравится миксовать геттеры с припиской is

  • @АлександрМельниченко-ц8л

    Синтетический UID первичный ключ занимает прилично больше места по сравнению с автоинкрементным int.
    И тянет за собой создание таких же по размеру полей-связей в других таблица.
    Плюс строки более тяжелые при индексации.
    В таких случаях индекс может занимать больше места чем сами данные таблицы.
    Разве это оправданно во всех случаях ?

    • @РоманГалиуллин-щ1п
      @РоманГалиуллин-щ1п 3 месяца назад +1

      Эм, uuid - это два лонга (16 байт), ну или 4 инта. Не такая большая разница.
      Другой вопрос, что индексируемое поле должно быть естественно сортируемым, а uuid v4 чистый рандом. Идеально подходит uuid v7, но в нескольких инстансах приложения непонятно будут ли коллизии

    • @АлександрМельниченко-ц8л
      @АлександрМельниченко-ц8л 3 месяца назад

      @@РоманГалиуллин-щ1п uuid4 это char(36) а это уже от 36 до 108 байт в зависимости от кодировки. Здесь разница уже на порядок по сравнению с int

    • @РоманГалиуллин-щ1п
      @РоманГалиуллин-щ1п 3 месяца назад

      ​​@@АлександрМельниченко-ц8лоткуда у тебя чар36? Почитай про uuid, а не только буквы считай :)
      Был бы он чар36, не было бы необходимости отдельный тип данных заводить под него

    • @АлександрМельниченко-ц8л
      @АлександрМельниченко-ц8л 3 месяца назад

      @@РоманГалиуллин-щ1п uuid это 36 символов по 1-3 байта (в зависимости от кодировки). А это уже на порядок больше int4

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +1

      У нас огромные базы с миллиардами записей и партиционированными таблицами, сотни таблиц с ключами UUID'ами, никогда не испытывали проблем по объему, обычно данные занимают больше места в разы. Поэтому считаю что оправдано во всех случаях)

  • @iziaslav931
    @iziaslav931 3 месяца назад +2

    А можно пару вопросов по внешним ключам?
    Если не делать внешние ключи, то индексы тоже не делать под колонки, где должны были быть эти внешние ключи?
    И как лучше получать единичную сложную сущность, которая раскидана по нескольким таблицам, если у нас нет внешних ключей?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +1

      Индексы нужны, если поле селективное. Отсутствие внешних ключей в бд не мешает вам собрать сущность джойнами или за несколько запросов

    • @4cd99e
      @4cd99e 3 месяца назад +2

      Индексы будут автоматически созданы для primary key второй таблице, которую хотите заджойнить, по крайней мере в Postgres.
      Джойны работают не только с внешними ключами, а с любыми полями.

  • @Mytest437
    @Mytest437 3 месяца назад +2

    Да что ж это такое твориться... до коли.... когда ж на курсах и в университетах будут учить тому, что нужно в работе 😁😁

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      Твой инстинкт разработчика должен заставить тебя постичь всё самостоятельно :)

  • @rtem-nuke
    @rtem-nuke 3 месяца назад +1

    Во многих сценариях @OneToMany и тд. действительно не нужны, но иногда есть необходимость в сложным запросах (фильтрации данных по любым полям) к связанным таблицам . И тогда реализация подобных запросов вручную, приведет к написанию своего фреймворка, а будет ли он лучше?)

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      Мы избегаем сложных запросов, разными способами, в том числе иногда используется денормализация данных. Своего фреймворка не понадобилось до сих пор )

  • @ТатьянаЮшинова-з7й
    @ТатьянаЮшинова-з7й 3 месяца назад +2

    Шанс, что uuid совпадут минимален, но не равен 0😊

  • @АнтонГордеев-ч6у
    @АнтонГордеев-ч6у 3 месяца назад +2

    То есть для каждого запроса нужно отдельную транзакцию? Или можно как-то работать с бд вообще без транзакций?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      > То есть для каждого запроса нужно отдельную транзакцию?
      SELECTы без транзакций, UPDATE/DELETE требуют транзакцию без вариантов.
      > Или можно как-то работать с бд вообще без транзакций?
      Вообще без транзакций - не думаю)

    • @АнтонГордеев-ч6у
      @АнтонГордеев-ч6у 3 месяца назад +2

      ​@@konstantin.kozlovskiy То есть проблема @Transactional в hibernate в том, что программист не задумывается, когда ставит аннотацию и транзакции создаются даже на select запросы?

    • @ЕвгенийГригорьев-ш9ц
      @ЕвгенийГригорьев-ш9ц 3 месяца назад

      @@konstantin.kozlovskiy UPDATE/DELETE всегда работает только внутри транзакции, даже если вы ее не намеренно не открыли

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +1

      да, и закрываются гораздо позже чем этого желает разработчик, иногда фатально позже) а иногда думает что транзакция должна была открыться и ему будут доступны данные через аннотацию @Lazy , а обращение происходит из того же класса и магии не случается.

    • @mrytsarev
      @mrytsarev 3 месяца назад +1

      @@konstantin.kozlovskiy select тоже может использовать транзакции, причем неявные.

  • @koanvic
    @koanvic 3 месяца назад +1

    uuid значительно медленнее. Если Вы формируете сложные запросы int будет в десятки а иногда и сотни раз быстрее - например если запрос выполняется 20 секунд с uuid, то с int 0.2

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      такой разницы быть не может, вероятно поле с uuid'ом было в вашем случае без индекса, против int поля с индексом

    • @koanvic
      @koanvic 3 месяца назад

      @@konstantin.kozlovskiy либо Вы тестируете либо я, но я ленивый)))
      нужно к примеру чтобы табличка скажем в мильён записей джоинилась сама с собой по инту и ууид

    • @koanvic
      @koanvic 3 месяца назад

      если у вас лапки дойдут до этого будет вам респект))) подпишусь и буду ждать)))

    • @alexanderspeshilov839
      @alexanderspeshilov839 3 месяца назад

      Код замеров в студию, пожалуйста. По моим ощущениям (и замерам 5-10 летней давности) - разница в производительности только в очень специфичных сценариях. Понятно, что всё что линейно зависит от объёма может стать медленнее: вставка, выборка миллионов записей, копирование в памяти на клиенте. Но это не очень большие затраты: в типичных OLTP системах типичная ширина записи 200-500 байт из которых 3-10 полей uuid. Если смотреть разницу между 64-битным int и 128-битным uuid, это будет 10-20% объёма. Ну ОК, именно эти поля обычно проиндексированы, возбьем 30%. Так что вот эти вот - достаточно органиченные - поерации станут дольше на 30%. Поиск по индексу (тем более по уникальному) будет практически как у int.
      Если у вас такая большая разница, то рискну предположить, что где-то происходит неэффективная и лишняя конвертация uuid в строку и обратно.

  • @eugenesmith9940
    @eugenesmith9940 3 месяца назад +1

    А как работаете с транзакциями? Вручную все?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +1

      в spring boot jpa есть конструкция transactionTemplate - это если несколько операций необходимо выполнить в рамках одной транзакции, а update и delete , прямо в @Repository помечаем @Transactional, т.е. без аннотации на методе репозитория работать не будет, но в прикладной код эта аннотация не выносится

  • @ВалерийКалачев-ы2ж
    @ВалерийКалачев-ы2ж 3 месяца назад

    Конечно странная аргументация приведена по поводу вреда разметки связей. Есть же механизм Lazy и entity graph которые решают задачи подобные. Может просто надо ими уметь пользоваться разработчикам..

  • @bobhutchinson3638
    @bobhutchinson3638 3 месяца назад +2

    На моменте 6:51 - вторичный и внешний ключ вы считаете одним и тем же? Или сначала вы рассказываете про вторичный, а потом про внешний?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +3

      одним и тем же, просто их по-разному называют (foreign key)

    • @ЕвгенийГригорьев-ш9ц
      @ЕвгенийГригорьев-ш9ц 3 месяца назад

      @@konstantin.kozlovskiy foreign key - НЕ ВТОРИЧНЫЙ КЛЮЧ! Это внешний ключ обеспечивающий REFERENTIAL INTEGRITY - ссылочную целостность то бишь

  • @codememory
    @codememory 3 месяца назад

    На счет связей ManyToOne, OneToMany, ты говори за что-то конкретное, а не в общем и целом, ибо есть фреймворки, которые не тянут связи, если ты не обратился к этой колонке, а возвращается просто пустая коллекция, как это сделано в доктрине. На счет enum, мол не юзайте, потому что вдруг в базу попадет значение которого в Enum нет и прога встанет. Может стоит на начать с того, а как они туда попадут, если изменения все делаются через фреймворки, а не напрямую в базе. Enum гарантирует наоборот, что в базу не попадет значение которого нет в Enum при добавлении или обновления базы через фреймворк
    На счет идентификации, мол, не юзайте автоинкрмент, потому что данные невозможно будет потом мигрировать, во время миграции данных, нельзя опираться на идентификатор и идентификаторы не мигрируруются, мигрируются только основные данные

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +1

      Иногда приходится менять данные в бд скриптами, когда аналитик на тестовом полигоне добавляет настройки какого-нибудь бизнес процесса и вписывает значение с русской буквой C вместо английской C в слове CONTRACT, а у тебя в приложении ENUM на колонку и из-за такого нелепого вмешательства твое приложение падает, это фиаско. По поводу миграции, в нашей ситуации бывает требуется настроить сложный договор силами команды разработки а затем смигрировать на другой полигон, в договоре десятки связанных сущностей, и для того чтобы обеспечить корректный "мердж" данных при последующих миграциях того же договора - без идентификаторов не обойтись

  • @ЕвгенийГригорьев-ш9ц
    @ЕвгенийГригорьев-ш9ц 3 месяца назад +2

    Вообще было бы неплохо перед тем как делать столь фривольное изложение манифеста про БД, узнать про сами БД чуть больше и глубже

  • @dos6920
    @dos6920 3 месяца назад

    Как ускорить запросы когда данных в таблице становиться несколько миллионов? Даже самы простые запросы выполняются по 10 секунд

    • @X7899X
      @X7899X 3 месяца назад

      Сделать шардинг и денормализацию как минимум

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      10 млн это мелочи, нужно посмотреть план запроса через EXPLAIN ANALYZE и вероятно ты увидишь sequence scan'ы, что покажет тебе вероятное отсутствие индекса на тех полях, которые используются в запросе

  • @victorbrylew1775
    @victorbrylew1775 3 месяца назад

    В случаях когда фреймворк не поддерживает автоматическое заполнение служебных полей использование default вполне ок. Разумеется только в таких полях и в таких фреймворках. В остальных случаях default зло.

  • @mr4erk
    @mr4erk 3 месяца назад +6

    Чет послушал: половина советов норм, половину на помойку. Но ситуация понятна, когда бизнес постоянно гонит делать быстрее, а ты тупо не успеваешь проанализировать ситуация и приходится говнокодить. В таких ситуациях это применимо. Но в действительно правильно структурированных организованных базах и проектах подобные советы просто идут нахер: учитесь работать с бд нормально, а не придумывать другие костыли, чтроб не сломались предыдущие

  • @Aleksey-n5h
    @Aleksey-n5h 3 месяца назад +1

    (У меня удаляется ответ)
    Таблица "profileInfo" доп. инфа о пользователе. Как лучше:
    1) сделать обоим pk. В таблице пользователя -> "profile_id" и таблице "profileInfo" -> "user_id" и сделать связи на прикладном уровне
    2) сделать обоим pk, но сделать развязочную таблицу для таблицы пользователя и таблицы "profileInfo"
    3) сделать в таблице "profileInfo" -> "user_id" и сделать его pk. (Будем запрашивать через "user_id")
    4) сделать в таблице "profileInfo" -> "user_id", в качестве pk в виде serial /identity для "profileInfo" (потому что запрашиваем через "user_id")
    Я знаю, что вы выберете 1, но почему все остальные варианты плохие?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  2 месяца назад

      pk - primary key должен быть в обоих таблицах (id) , в любом случае, мы считаем это как само собой разумеющееся
      развязочная таблица многие ко многим нужна только если у вас по бизнес смыслу юзеры могут иметь несколько профилей, и один профиль может быть привязан к нескольким юзерам
      п3. ошибочный, нельзя в таблице профилей сделать поле user_id pk, что-то здесь не так ) либо вы говорите про связь 1к1 либо имеете ввиду что-то другое
      п4 не совсем понял

    • @Aleksey-n5h
      @Aleksey-n5h 2 месяца назад

      ​@@konstantin.kozlovskiy да. Связь 1к1? и еще вопрос. Вы сказали, что составных ключей не делаете. Даже для развязочных таблиц типа users_roles? Учитывая, что мы знаем, что там будут только users и roles, вы все равно делаете uuid для pk?

    • @Aleksey-n5h
      @Aleksey-n5h 2 месяца назад

      @@konstantin.kozlovskiy п4 - это создать для "profileInfo" для pk тип serial/identity columns, что бы просто id был и запрашивать "profileInfo" через "user_id"

    • @Aleksey-n5h
      @Aleksey-n5h 2 месяца назад

      @@konstantin.kozlovskiy п4. pk тип serial/identity для profileInfo, что бы был id и запрашивать profileInfo через user_id, т.к 1к1

    • @Aleksey-n5h
      @Aleksey-n5h 2 месяца назад

      @@konstantin.kozlovskiy я изменил п4 потому что ютуб опять удаляет коммент

  • @ReezonBeatbox
    @ReezonBeatbox 3 месяца назад +5

    как часто вы мигрируете данные? судя по советам, вы чуть не каждый день гоняете данные из одной бд в другую, и вам все мешает)

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      Довольно часто новые слуги и настройки переводов готовят на тесте и мигрируют в пром

  • @adoloro
    @adoloro 3 месяца назад

    Короче, я не понял почему вы не взяли mongo.

  • @Aleksey-n5h
    @Aleksey-n5h 3 месяца назад

    Если у нас есть таблица "profile_info". Можно ли для нее сделать primary Key в виде "user_id"? Или лучше serial в качестве PK, так как особо этот id и не нужен.

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      Не совсем понял что хранит таблица profile_info. Если она хранит профили пользователей, которые хранятся в таблице users, то в таблице profile_info не нужен user_id, скорее всего должен быть profile_id:uuid в таблице users (тут зависит от бизнес процессов). Я бы в обоих таблицах завел PK id:uuid. Уточните вопрос )

    • @Aleksey-n5h
      @Aleksey-n5h 3 месяца назад

      @@konstantin.kozlovskiy да. Таблица profile_info хранит доп информацию о пользователе. Зачем делать id для profile_info. Не проще ли запрашивать эту таблицу через "user_id", если она всегда будет одна для каждого пользователя? И сделать "user_id" в качестве pk. Просто зачем генерировать по миллиону ключей, отправлять эти id profile на клиент, тратить на это время, хоть и малое, если можно либо запрашивать через "user_id" и сделать его pk или сделать pk в виде serial. Смысла особо этот serial нести не будет, так как исп. "user_id"

    • @Aleksey-n5h
      @Aleksey-n5h 3 месяца назад

      ​@@konstantin.kozlovskiy Таблица "profile_info" хранит доп. информацию о пользователе (имя, фамилию, день рождение и т.д). Почему нельзя сделать в ней "user_id" и запрашивать ее через "user_id" (как внешний ключ, только на уровне прикладном)? Или сделать вообще в ней "user_id" в качестве pk? Ключ, сгенерированный для "profile_info" никак не поможет и вот вопрос. Лучше сделать:
      1) каждому отдельно pk: в таблице пользователя "profile_id", в таблице "profileInfo" -> "user_id"
      2) сделать каждому отдельно pk, но при этом добавить развязочную таблицу (хотя там связь будет "один к одному" и смысла тоже я тут не вижу)
      3) В "profileInfo" сделать "user_id" и это в качестве pk, что бы не создавать лишние id, потому что мы все равно будем запрашивать по "user_id", если такой вариант
      4) В "profileInfo" сделать "user_Id" и "id" pk в качестве serial (потому что мы будем запрашивать по "user_id"
      Я не понимаю, смысл создавать id, которые не особо несут какого-то смысла, если можно запросить в качестве связи на прикладном уровне.
      И я так понимаю, это тема связанна с нормальными формами. И вот еще вопрос: если ты следуешь нормальной форме определенной, то она должна распространяться на все таблицы?

    • @Aleksey-n5h
      @Aleksey-n5h 3 месяца назад

      ​@@konstantin.kozlovskiy таблица "profileInfo" доп. инфа о пользователе. Как лучше:
      1) сделать обоим pk. В таблице пользователя -> "profile_id" и таблице "profileInfo" -> "user_id" и сделать связи на прикладном уровне
      2) сделать обоим pk, но сделать развязочную таблицу для таблицы пользователя и таблицы "profileInfo"
      3) сделать в таблице "profileInfo" -> "user_id" и сделать его pk. (Будем запрашивать через "user_id")
      4) сделать в таблице "profileInof" -> "user_id", связать внешним (прикладным) ключом и "id" в качестве pk в виде serial (потому что запрашиваем через "user_id")
      Я знаю, что вы выберете 1, но почему все остальные варианты плохие?

    • @Aleksey-n5h
      @Aleksey-n5h 3 месяца назад

      @@konstantin.kozlovskiy Таблица "profileInfo" доп. инфа о пользователе. Как лучше:
      1) сделать обоим pk. В таблице пользователя -> "profile_id" и таблице "profileInfo" -> "user_id" и сделать связи на прикладном уровне
      2) сделать обоим pk, но сделать развязочную таблицу для таблицы пользователя и таблицы "profileInfo"
      3) сделать в таблице "profileInfo" -> "user_id" и сделать его pk. (Будем запрашивать через "user_id")
      4) сделать в таблице "profileInof" -> "user_id", связать внешним (прикладным) ключом и "id" в качестве pk в виде serial (потому что запрашиваем через "user_id")
      Я знаю, что вы выберете 1, но почему все остальные варианты не очень?

  • @user-0xDEEDBEEF
    @user-0xDEEDBEEF 3 месяца назад +2

    Ммм.. технологии джависта, использовать СУБД как хранилище.
    FYI Аудит лучше делать на триггерах или stored procedures если доступ к базе через процедурный интерфейс

  • @Mytest437
    @Mytest437 3 месяца назад +1

    Если взять итог по поводу ИД: он должен не нести НИКАКОЙ смысловой нагрузки, потому что смыслы в жизни людей не постоянны. И должен быть максимально уникальный, не привязанный этой уникальностью тоже ни к чему. Просто точка уникальности.

  • @maxproteinov9660
    @maxproteinov9660 3 месяца назад +2

    Не побоюсь показаться глупым, ведь я действительно не очень силён в реляционных БД.
    Не очень понял часть про неиспользование внешних ключей. Тогда каким образом БД понимает, какие сущности могут быть связанны с другими?
    Например, как она соединит Useŕа и Tenant´а, если в пользователе не заложен ID этого самого Tenant´а?
    PS: выглядит как вызов пояснительной бригады😅 Заранее благодарю!

    • @svitboomer8840
      @svitboomer8840 3 месяца назад +2

      Тут дело в "создании", как сущности в БД, а не поля, например, post_id в таблице users. Если я правильно понял

    • @maxproteinov9660
      @maxproteinov9660 3 месяца назад +1

      @@svitboomer8840благодарю!) Понадоедал своим коллегам, они мне объяснили, что при создании таблицы можно указывать FK, если этот ID куда-либо ссылается, либо не указывать, но здесь вся забота при соединении таблиц на тебе.

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +1

      поля указывающие на идентификаторы других сущностей (зависимых) создавать конечно нужно, но не обязательно помечать их foreign key

    • @maxproteinov9660
      @maxproteinov9660 3 месяца назад +1

      @@konstantin.kozlovskiy спасибо большое за материал, было очень интересно и полезно!

  • @klim0v
    @klim0v 3 месяца назад

    А поиск по uuid сравним с поиском по int?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      пишут что плюс минус одно и тоже, разница в несколько %, но это запросто кроется преимуществом в других операциях, когда тебе не нужно просить sequence в бд сгенерировать тебе автоинкрементное значение

    • @KonstantDev
      @KonstantDev 3 месяца назад +3

      @@konstantin.kozlovskiy в дефолтном btree индексе int инкрементится и добавляется практически без перестроек, с uuid необходимо переупорядочивать всё часто, перформанс у вас от этого разве не проседает?

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      так далеко не копал, перфоманс в порядке на сотнях миллионов записей, запросы по индексам (по uuid полям) выполняются за миллисекунды

    • @slashfast
      @slashfast 3 месяца назад

      Есть уже uuid7

    • @KonstantDev
      @KonstantDev 3 месяца назад

      @@slashfast это согласен

  • @stone8905
    @stone8905 2 месяца назад

    UUID пойдет только для малого количества проектов. UUID занимает больше места, проблемы с индексацией (index fragmentation) будет все хуже и хуже. рандомность uuid боком обернется при инсертах, индексы будут корявые а не по порядку. а ведь есть еще понятие clustered and non clustered index, если вы сделали UUID pk то записи будут создаваться бардачно.
    nullable - это норм, какая разница менять валидацию в сервисах или nullable на true сделать?
    комментарии - а если это мне не удобно???????????????????????????? (говоришь как будто это всем удобно)
    не использования аннотации ManyToOne OneToMany и ManyToMany и тд, я согласен, без них жизнь становится все лучше и лучше. это единственный хороший совет а остальное это для веганов.

  • @KimBogdanov
    @KimBogdanov 3 месяца назад +1

    Все четко и по делу! 👍👍👍👍

  • @damir8847
    @damir8847 3 месяца назад +5

    Полезное и интересное видео. Узнал для себя что-то новое. Константин, спасибо! 👍🤝
    Интересно узнать еще о HighLoad правилах в рамках Ваших команд. В идеале какой-то github-репозиторий в котором собраны все эти правила в виде чеклиста)) 🙌

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +3

      привет, спасибо, планирую еще ряд роликов на похожие темы, про обратную совместимость и фиче-тоглы )

    • @Дмитрий-ю9к3г
      @Дмитрий-ю9к3г 3 месяца назад

      @@konstantin.kozlovskiy про фича-тоглы очень классно было бы посмотреть!

  • @ЕвгенийГригорьев-ш9ц
    @ЕвгенийГригорьев-ш9ц 3 месяца назад

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

    • @mrytsarev
      @mrytsarev 3 месяца назад

      Что за внутренние ключи?

  • @ЕвгенийГригорьев-ш9ц
    @ЕвгенийГригорьев-ш9ц 3 месяца назад

    генерация UUID требует гооораздо большего времени чем автоинкремента - если что вдруг про хайлоад речь зашла тут

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      UUID генерирует твоя программа, а автоинкрементное значение база данных при вставке, и все запросы "условно" встают в очередь пока один sequence выдаст nextval для тебя, это тоже работает быстро, но вот быстрее ли UUID'а - не уверен

    • @ЕвгенийГригорьев-ш9ц
      @ЕвгенийГригорьев-ш9ц 3 месяца назад

      @@konstantin.kozlovskiy это еще хуже! Так как внутренние ключи должна генерить БД - это бэст практис, твоя программа в отличном случае вообще не должна оперировать внутренними ключами БД

  • @andrewkruchini8614
    @andrewkruchini8614 2 месяца назад

    "Приходит заказчик, и..." - интересный подход к разработке. А в перспективе это оправдывается? Например, Вы пробовали для сравнения более тщательное проектирование БД вместо того, чтобы идти на поводу у заказчика? Конечно, я не знаю специфики Вашего бизнеса, потому такие вопросы. Просто, пока вижу некоторые советы, здравые для максимально быстрой реакции на капризы заказчика, но не для максимально быстрой работы с БД (кроме "не держите открытыми транзакции"). Тогда зачем "HighLoad " в названии ролика?.. :/

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  2 месяца назад

      Сколько тщательно не планируй базу, если меняется бизнес требование, разработка должна отреагировать, в процессе работы всегда встает вопрос "сделать гибко заранее предусмотрев капризы заказчика" или "сделать именно так как просит заказчик", от принятого решения может зависеть многое, бизнес зарабатывает для нас деньги, и мы не должны отказывать с позиции "мы тут технически придумали что будет вот так, а твоя хотелка не вписывается в наш профиль нагрузки". История с nullable полями нас никогда не подводила, и работать в таком режиме по итогу гораздо комфортнее. Технически это не несет никаких сложностей или проблем с производительностью, поэтому тут я не совсем понял про скорость. Мы работаем именно так, чтобы было максимально быстро.

  • @mr4erk
    @mr4erk 3 месяца назад +1

    В общем попросили не ругать за правила для работы с жутким легаси с отсутствующей организацией структуры данных. окей.

  • @mr4erk
    @mr4erk 3 месяца назад +9

    нейминг: я бы поспорил только в одном моменте - множественное число.

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      тут да, вкусовщина

    • @mr4erk
      @mr4erk 3 месяца назад +6

      @@konstantin.kozlovskiy нет. бскорее просто какой-то невнятный договор программистов (архитекторов баз данных?)
      банально там далеко не всегда только s подставить: y - ies, f - ves. При том модели представляющие запись таблицы внезапно в единственном числе все. И банально это может очень много путаницы внести, особенно есди очередной джун не то чтобы хорошо знает инглиш

    • @Alexander-is1eq
      @Alexander-is1eq 3 месяца назад +1

      А person во множественном числе и вовсе будет people. ))

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +1

      какая путаница может быть если модель называется Transaction.java а таблица transactions , а если слово плохо склоняется, добавляем *_entries, например summary_data_entries

    • @mr4erk
      @mr4erk 3 месяца назад +1

      @@konstantin.kozlovskiy и нахуя больше правил и исключений? Кому оно надо?

  • @TitikUniverse
    @TitikUniverse 3 месяца назад

    Извиняюсь за негативный комментарий заранее, но я пытался послушать до 51 секунды 4 раза и всё равно ничего не уловил. Голос очень монотонный, размеренный, медленный и сверх спокойный. Я бы его применил в приложении с звуками для засыпания

  • @ЕвгенийГригорьев-ш9ц
    @ЕвгенийГригорьев-ш9ц 3 месяца назад +8

    Если вы используете БД в столь примитивном ключе - без FK без ENUMs без AK без default-ов - то вообще зачем вам реляционная БД ? И вообще постгрес зачем вам? Есть подозрение, что в вашей консерватории из яиц вылупились не куры а черти- что, как в известном рассказе Булгакова.

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад +1

      ACID как никак на дороге не валяется )

    • @ЕвгенийГригорьев-ш9ц
      @ЕвгенийГригорьев-ш9ц 3 месяца назад

      @@konstantin.kozlovskiy кроме как в постгресе ACID нигде не присутсвует?

    • @profile_pub190
      @profile_pub190 3 месяца назад +3

      вот тоже слушал и думал что парни страдают делая nosql из sql

    • @techrkru
      @techrkru 3 месяца назад

      база ,в первую очередь, это база. даже в моей системе ,автоматизация общепита , используется не более 5-и ф-ций и-то из-за того что долбанный Честный Знак нужно в реал-тайме проверять .. а ну еще notify юзаю,чтоб разные системы не селектили по таймеру 1 и ту же таблицу (вернее селектили,но не ежесекундно :-) )

  • @yashkevich8164
    @yashkevich8164 3 месяца назад +2

    Откажитесь от фреймворка, чего уж мелочиться

  • @ЕвгенийГригорьев-ш9ц
    @ЕвгенийГригорьев-ш9ц 3 месяца назад

    если в таблице "миллиард" записей - то это плохая история дла постгреса и тут стоит думать про какй-нть другой движок БД

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      нормальная история с учетом того, что таблица партиционирована, кто запретил хранить в базе много записей?)

    • @mrytsarev
      @mrytsarev 3 месяца назад

      А чем именно плоха эта история?

  • @ЕвгенийГригорьев-ш9ц
    @ЕвгенийГригорьев-ш9ц 3 месяца назад +2

    Не ВТОРИЧНЫЕ клбчи а ВНЕШНИЕ КЛЮЧИ FOREIGH KEYS то бишь !!!!!! Дядя!

    • @mjeday
      @mjeday 3 месяца назад +1

      Я даже гуглил, что такое вторичные ключи. Пока гуглил, речь пошла про foreign keys, и гугление прекратилось )

  • @chuyashenko
    @chuyashenko 3 месяца назад

    Это "вредные советы", почти все...

  • @timur2887
    @timur2887 3 месяца назад

    Давайте посмотрим правде в глаза: просто вы не умеете работать с внешними ключами) Как-то все умеют данные вставлять с ними, а вы почему-то не научились))) Очередная команда, которая уверена, что знает что-то лучше других, а по факту наоборот) Куча каких-то самопридуманных правил, не надо такое выдавать за истину и учить этому других людей

    • @konstantin.kozlovskiy
      @konstantin.kozlovskiy  3 месяца назад

      Я привел в видео достаточно аргументов за и против внешних ключей, сказал в каких ситуациях они нужны, а в каких ситуациях их использование невозможно. С этим бессмысленно спорить. Внешние ключи это инструмент, который нужно правильно использовать. Я лишь подсветил что для высоконагруженных систем и ентерпрайз специфики внешние ключи чаще всего не нужны. Если у вас свой проект или небольшая организация, пожалуйста, используйте внешние ключи.

  • @user-lv3hn6uz4e
    @user-lv3hn6uz4e 3 месяца назад

    Короче очень слабое видео) много спорных моментов.
    11:40 last user modify и last time modify обычно как раз null по умолчанию, так как строка может быть ни разу не изменина