Комментарии везде и это удобно....Просто браво....Особенно это удобно в полях типа fio, name, surname, middlename, age, status и тд....Действительно, если бы не было комментариев, хер бы кто догадался что это такое!!!
Мои пять копеек: - Присоединяюсь к тому, что 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 и КХД) должны ходить через сервис. Структура базы - никакая её часть - не должны становиться контрактом сервиса. - Про триггеры в целом согласен, но с дополднением. Если уж надо что-то "типа триггеров", то посмотрите на материализованные представления. Но аккуратно. Нет, пункты не кончились, просто писать надоело :)
Вопросы. Большое спасибо за видео 1. Кто гененирует uuid записи, приложения или бд? 2. Кто дает гарантии что uuid будет уникальным или мы подагаемся на спецификацию (особенно актуально если нужно обьеденить данные из нескольких кластеров) 3. Как часто используюься сложные типы такие характерные для pg ( jsonb, hstore, array и т.д)
1. Приложение. 2. Сам UUID и дает гарантии, вероятность совпадения двух правильно сгенерированных UUID практически равна нулю (она настолько низка что тебе дадут премию если ты сгенерируешь коллизию. 3. Для хранения таких данных мы не используем PostgreSQL, тут лучше подойдет что-нибудь нереляционное вроде Redis
По первому я бы сказал база, но в редких случаях проще на стороне приложения сгенерировать и сразу в несколько других таблиц прокинуть uuid. Хотя, конечно, можно сначала запросить у базы, а потом уже прокидывать. Короч, зависит от задачи, но по дефолту на стороне базы
По поводу имен таблиц, есть еще мнение именовать в единственном числе, чтобы писать более читаемые запросы. Например 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-аннотаций. У меня на работе такое же мнение относительно этих штук.
когда набирают запросы руками, в 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 ```
Очень необычные, половинчатые советы. Например про UUIID id... Во первых, нет математической гарантии их неповторямости... и использовать UUID рационально только как дополнительное поле в качестве вторичного ID для связей между фрагментами распределенной БД... а для связей в локальных БД ясен перед должен использоваться быстрый и компактный INTEGER id... и самое главное для большой БД особенно на механических HDD - во избежание будущих тормозов на SELECT-запросах ни в коем случае нельзя создавать индексы по полям и функциям, которые участвуют в запросах и одинаковые значения которых равномерное распылены по всему объему записей - например по кодам операторов, по типам документов, по суммам платежей и т.д.
На моменте 10:35, с версии postgresql 11, добавление колонки с дефолт значением в существующую таблицу, не вызовет перезаписи значений по всех строках.
Если вы не используете increment, то что вы используете для справочных таблиц для pk, например "colors", "sizes", "types", "materials"? Естественный id или сгенерированный? Если естественный, то как вы называете? "id" или "name"
Спасибо за советы. Может у вас есть подходы к нормализации-денормализации бд раз вы не используете вторичные ключи и полагаетесь на уровень приложения? Сам много лет придерживаюсь подобных правил по неймингу, служебным полям и связям между сущностями. Также интересно узнать как вы проводите оптимизация работы вашего приложения в контексте базы данных. Спасибо за ответ :)
Денормализация строится в зависимости от бизнес процессов. Иногда, в угоду производительности выгодно дублировать данные в некоторых таблицах. Чаще всего мы добавляем элементы денормализации в виде ссылок между сущностями. Например: транзакция имеет ссылку на услугу, услуга имеет ссылку на договор, договор имеет ссылку на филиал. Бизнес процесс должен обработать все транзакции по филиалу Х. Вместо того чтобы в момент работы бизнес процесса тянуть все 3 сущности из базы (замечу, что в микросервисной архитектуре эти сущности принадлежат разным сервисам и могут лежать в разных базах), мы (в момент обработки транзакции) проставляем на транзакции идентификатор филиала, для упрощения работы другим бизнес процессам.
@@konstantin.kozlovskiy Спасибо за ответ. У себя на практике архитектурно использую подход Data vault в виде таблиц сущностей и словарей свойств плюс таблицы отношений, часто он приводит к денормализации, когда бизнес логика лежит в приложении.
Как вариант, можно приложения писать таким образом, чтобы при поиске строк не использовать первичный ключ, а другие уникальные поля с индексами, тогда не будет необходимости, чтобы один и тот же инстанс сущности имел одинаковые id в разных окружениях
приведи примеры, использовать там где нужны строки, не использовать там где НЕ нужны строки, все просто )) что text что varchar для PostgreSQL разницы нет, в последних версиях рекомендуют даже размерность не указывать
Привет! Интересное видео, возникло несколько вопросов: 1. Почему вы используете реляционную бд, а не nosql? 2. Какой либой вы получаете данные из бд? 3. Если вы не используете @Transactional, то не боитесь ли грязного чтения?
1. Мы не используем Nosql потому что нам нужна Sql база данных, реляционная. Я не понимаю почему вообще может возникнуть такой вопрос, у нас не документы, у нас транзакции и зависимые сущности которые мы раскладываем по таблицам и собираем в разном объеме при необходимости. Нет у нас JSON документов которые нужно складывать по ID и доставать по ID. 2. Мы используем Hibernate 3. Если мы не используем аннотацию @Transactional это не значит что у нас произойдет грязное чтение, через фреймворк любые изменения данных аля update / delete все равно пойдут в транзакции, аннотация будет стоять но на самом низко-уровневом методе репозитория а не где-то в слое бизнес логики. Грязных чтений не будет.
А если вам надо сделать операцию в БД, а потом логику и ещё раз операцию в БД. Если у вас транза в репо только то нет единой атомарности этих операций.
Убрали мы внешний индекс и для целостности данных нам нужно тогда сделать как минимум один select с limit 1 для проверки. Это оправданно к примеру когда данных на столько много что пересчет дерева занимает больше времени чем операция select. Но сразу несем потери из-за сетевых транзакций. Допустим две таблицы по 10 млн записей. И пришел к нам тот самый select перед вставкой. И так получилось, что нужное поле в самом конце. Postgres пробежит по всем строкам. Ок! Программист тогда понимает что это долго и берет индексирует поле. Ждет пол дня а потом радуется. Но тогда в чем был смысл вообще не ставить индексы в самом начале. На то они и реляционные базы данных что-бы иметь структуру данных. Есть другие базы типа Cassandra как раз для таких задач конечно там есть и свои нюансы. Потому всегда нужно выбирать компромис.
Внешний ключ проверяет целостность при любых манипуляциях с зависимыми данными. Если тебе нужно вставить 10млн записей в одну таблицу и 10 млн записей в другую, и они связаны. Без внешних ключей ты можешь делать это параллельно и с более высокой производительностью. Да есть риск - если у тебя в коде баг, возможно нарушение целостности. Но как я упоминал в видео, если ты связал две таблицы внешним ключом и решил их партиционировать (у нас же уже 10 млн и с каждым днем всё прибывает), то ничего не выйдет)
Синтетический UID первичный ключ занимает прилично больше места по сравнению с автоинкрементным int. И тянет за собой создание таких же по размеру полей-связей в других таблица. Плюс строки более тяжелые при индексации. В таких случаях индекс может занимать больше места чем сами данные таблицы. Разве это оправданно во всех случаях ?
Эм, uuid - это два лонга (16 байт), ну или 4 инта. Не такая большая разница. Другой вопрос, что индексируемое поле должно быть естественно сортируемым, а uuid v4 чистый рандом. Идеально подходит uuid v7, но в нескольких инстансах приложения непонятно будут ли коллизии
@@АлександрМельниченко-ц8лоткуда у тебя чар36? Почитай про uuid, а не только буквы считай :) Был бы он чар36, не было бы необходимости отдельный тип данных заводить под него
У нас огромные базы с миллиардами записей и партиционированными таблицами, сотни таблиц с ключами UUID'ами, никогда не испытывали проблем по объему, обычно данные занимают больше места в разы. Поэтому считаю что оправдано во всех случаях)
Со многим согласен, вопрос про внешние ключи. Мне не совсем понятен ваш концепт. Например есть таблицы users и transactions. Например в transactions уже 40 млн записей. В ней есть поле user_id, и когда я хочу получить последние 10 транзакций определённых пользователей я так или иначе буду их отсеивать по полю user_id, например соберу id нужных пользователей и буду проверять вхождение в этот массив. Ну и для этого я наброшу на поле user_id индекс, чтобы поиск был быстрым. И по сути это можно назвать внешним ключом. Точнее реализовать это можно как внешний ключ. Расскажите как вы реализуете подобные выборки, отдельно ли индекс создаёте в приложении?
индексы конечно нужны, но если у тебя две транзакционных таблицы, например transactions & transaction_data, и между ними есть связь, обе таблицы огромные и тебе необходимо их партиционировать, то между ними foreign ключей быть не может, а индексы конечно быть должны. если рассматривать таблицы users & transactions то вероятно таблицу пользователей ты захочешь шардировать и тут foreign ключи тоже тебе помешают. Такие ключи не доставляют неудобств только между таблицами справочниками, в которых не часто происходит вставка и которые не нужно шардировать или партиционировать
"Переливать данные между полигонами" типа скорпировать часть данных с прода на QA сервер? как это технически делают? Как принято делать в больших конорах?
Данные с прода на тестовые полигоны не заливают, это нарушает десятки правил) с теста на прод бывает, настраивают сложный договор на тесте, проверяют и переносят. Также распространена миграция данных между разными тестовыми полигонами
@ если генерация синтетических данных для наполнения тестового контура невозможна, прибегают к процедуре обезличивания, этим занимаются целые команды, базу загружают в специальное место где каждую колонку с данными проверяют и подвергают обезличиванию с сохранением бизнес целостности, т.е если в базе ИНН организации встречается в нескольких местах, его обезличивают везде одинакого. Это очень сложная и кропотливая процедура.
А можно пару вопросов по внешним ключам? Если не делать внешние ключи, то индексы тоже не делать под колонки, где должны были быть эти внешние ключи? И как лучше получать единичную сложную сущность, которая раскидана по нескольким таблицам, если у нас нет внешних ключей?
Индексы будут автоматически созданы для primary key второй таблице, которую хотите заджойнить, по крайней мере в Postgres. Джойны работают не только с внешними ключами, а с любыми полями.
Во многих сценариях @OneToMany и тд. действительно не нужны, но иногда есть необходимость в сложным запросах (фильтрации данных по любым полям) к связанным таблицам . И тогда реализация подобных запросов вручную, приведет к написанию своего фреймворка, а будет ли он лучше?)
на самом деле это вкусовщина, просто когда будете мапить эти поля в java, геттеры будут генерироваться isActive(), а для других полей getXXX(), мне не нравится миксовать геттеры с припиской is
Вот только автор забыл сказать что uuid должен быть timebased uuid v7 иначе постгрес будет ребалансировать свое B-tree после каждой вставки, а лучше всего просто взять timebased long, что-то вроде snowflake ID который и место меньше займёт и выглядит нормально и работать будет быстрее классического uuid.
Полезное и интересное видео. Узнал для себя что-то новое. Константин, спасибо! 👍🤝 Интересно узнать еще о HighLoad правилах в рамках Ваших команд. В идеале какой-то github-репозиторий в котором собраны все эти правила в виде чеклиста)) 🙌
Чет послушал: половина советов норм, половину на помойку. Но ситуация понятна, когда бизнес постоянно гонит делать быстрее, а ты тупо не успеваешь проанализировать ситуация и приходится говнокодить. В таких ситуациях это применимо. Но в действительно правильно структурированных организованных базах и проектах подобные советы просто идут нахер: учитесь работать с бд нормально, а не придумывать другие костыли, чтроб не сломались предыдущие
> То есть для каждого запроса нужно отдельную транзакцию? SELECTы без транзакций, UPDATE/DELETE требуют транзакцию без вариантов. > Или можно как-то работать с бд вообще без транзакций? Вообще без транзакций - не думаю)
@@konstantin.kozlovskiy То есть проблема @Transactional в hibernate в том, что программист не задумывается, когда ставит аннотацию и транзакции создаются даже на select запросы?
да, и закрываются гораздо позже чем этого желает разработчик, иногда фатально позже) а иногда думает что транзакция должна была открыться и ему будут доступны данные через аннотацию @Lazy , а обращение происходит из того же класса и магии не случается.
uuid значительно медленнее. Если Вы формируете сложные запросы int будет в десятки а иногда и сотни раз быстрее - например если запрос выполняется 20 секунд с uuid, то с int 0.2
@@konstantin.kozlovskiy либо Вы тестируете либо я, но я ленивый))) нужно к примеру чтобы табличка скажем в мильён записей джоинилась сама с собой по инту и ууид
Код замеров в студию, пожалуйста. По моим ощущениям (и замерам 5-10 летней давности) - разница в производительности только в очень специфичных сценариях. Понятно, что всё что линейно зависит от объёма может стать медленнее: вставка, выборка миллионов записей, копирование в памяти на клиенте. Но это не очень большие затраты: в типичных OLTP системах типичная ширина записи 200-500 байт из которых 3-10 полей uuid. Если смотреть разницу между 64-битным int и 128-битным uuid, это будет 10-20% объёма. Ну ОК, именно эти поля обычно проиндексированы, возбьем 30%. Так что вот эти вот - достаточно органиченные - поерации станут дольше на 30%. Поиск по индексу (тем более по уникальному) будет практически как у int. Если у вас такая большая разница, то рискну предположить, что где-то происходит неэффективная и лишняя конвертация uuid в строку и обратно.
в spring boot jpa есть конструкция transactionTemplate - это если несколько операций необходимо выполнить в рамках одной транзакции, а update и delete , прямо в @Repository помечаем @Transactional, т.е. без аннотации на методе репозитория работать не будет, но в прикладной код эта аннотация не выносится
(У меня удаляется ответ) Таблица "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, но почему все остальные варианты плохие?
pk - primary key должен быть в обоих таблицах (id) , в любом случае, мы считаем это как само собой разумеющееся развязочная таблица многие ко многим нужна только если у вас по бизнес смыслу юзеры могут иметь несколько профилей, и один профиль может быть привязан к нескольким юзерам п3. ошибочный, нельзя в таблице профилей сделать поле user_id pk, что-то здесь не так ) либо вы говорите про связь 1к1 либо имеете ввиду что-то другое п4 не совсем понял
@@konstantin.kozlovskiy да. Связь 1к1? и еще вопрос. Вы сказали, что составных ключей не делаете. Даже для развязочных таблиц типа users_roles? Учитывая, что мы знаем, что там будут только users и roles, вы все равно делаете uuid для pk?
@@konstantin.kozlovskiy п4 - это создать для "profileInfo" для pk тип serial/identity columns, что бы просто id был и запрашивать "profileInfo" через "user_id"
В случаях когда фреймворк не поддерживает автоматическое заполнение служебных полей использование default вполне ок. Разумеется только в таких полях и в таких фреймворках. В остальных случаях default зло.
На счет связей ManyToOne, OneToMany, ты говори за что-то конкретное, а не в общем и целом, ибо есть фреймворки, которые не тянут связи, если ты не обратился к этой колонке, а возвращается просто пустая коллекция, как это сделано в доктрине. На счет enum, мол не юзайте, потому что вдруг в базу попадет значение которого в Enum нет и прога встанет. Может стоит на начать с того, а как они туда попадут, если изменения все делаются через фреймворки, а не напрямую в базе. Enum гарантирует наоборот, что в базу не попадет значение которого нет в Enum при добавлении или обновления базы через фреймворк На счет идентификации, мол, не юзайте автоинкрмент, потому что данные невозможно будет потом мигрировать, во время миграции данных, нельзя опираться на идентификатор и идентификаторы не мигрируруются, мигрируются только основные данные
Иногда приходится менять данные в бд скриптами, когда аналитик на тестовом полигоне добавляет настройки какого-нибудь бизнес процесса и вписывает значение с русской буквой C вместо английской C в слове CONTRACT, а у тебя в приложении ENUM на колонку и из-за такого нелепого вмешательства твое приложение падает, это фиаско. По поводу миграции, в нашей ситуации бывает требуется настроить сложный договор силами команды разработки а затем смигрировать на другой полигон, в договоре десятки связанных сущностей, и для того чтобы обеспечить корректный "мердж" данных при последующих миграциях того же договора - без идентификаторов не обойтись
Ммм.. технологии джависта, использовать СУБД как хранилище. FYI Аудит лучше делать на триггерах или stored procedures если доступ к базе через процедурный интерфейс
Конечно странная аргументация приведена по поводу вреда разметки связей. Есть же механизм Lazy и entity graph которые решают задачи подобные. Может просто надо ими уметь пользоваться разработчикам..
Если у нас есть таблица "profile_info". Можно ли для нее сделать primary Key в виде "user_id"? Или лучше serial в качестве PK, так как особо этот id и не нужен.
Не совсем понял что хранит таблица profile_info. Если она хранит профили пользователей, которые хранятся в таблице users, то в таблице profile_info не нужен user_id, скорее всего должен быть profile_id:uuid в таблице users (тут зависит от бизнес процессов). Я бы в обоих таблицах завел PK id:uuid. Уточните вопрос )
@@konstantin.kozlovskiy да. Таблица profile_info хранит доп информацию о пользователе. Зачем делать id для profile_info. Не проще ли запрашивать эту таблицу через "user_id", если она всегда будет одна для каждого пользователя? И сделать "user_id" в качестве pk. Просто зачем генерировать по миллиону ключей, отправлять эти id profile на клиент, тратить на это время, хоть и малое, если можно либо запрашивать через "user_id" и сделать его pk или сделать pk в виде serial. Смысла особо этот serial нести не будет, так как исп. "user_id"
@@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, которые не особо несут какого-то смысла, если можно запросить в качестве связи на прикладном уровне. И я так понимаю, это тема связанна с нормальными формами. И вот еще вопрос: если ты следуешь нормальной форме определенной, то она должна распространяться на все таблицы?
@@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, но почему все остальные варианты плохие?
@@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, но почему все остальные варианты не очень?
10 млн это мелочи, нужно посмотреть план запроса через EXPLAIN ANALYZE и вероятно ты увидишь sequence scan'ы, что покажет тебе вероятное отсутствие индекса на тех полях, которые используются в запросе
Если взять итог по поводу ИД: он должен не нести НИКАКОЙ смысловой нагрузки, потому что смыслы в жизни людей не постоянны. И должен быть максимально уникальный, не привязанный этой уникальностью тоже ни к чему. Просто точка уникальности.
Не побоюсь показаться глупым, ведь я действительно не очень силён в реляционных БД. Не очень понял часть про неиспользование внешних ключей. Тогда каким образом БД понимает, какие сущности могут быть связанны с другими? Например, как она соединит Useŕа и Tenant´а, если в пользователе не заложен ID этого самого Tenant´а? PS: выглядит как вызов пояснительной бригады😅 Заранее благодарю!
@@svitboomer8840благодарю!) Понадоедал своим коллегам, они мне объяснили, что при создании таблицы можно указывать FK, если этот ID куда-либо ссылается, либо не указывать, но здесь вся забота при соединении таблиц на тебе.
UUID пойдет только для малого количества проектов. UUID занимает больше места, проблемы с индексацией (index fragmentation) будет все хуже и хуже. рандомность uuid боком обернется при инсертах, индексы будут корявые а не по порядку. а ведь есть еще понятие clustered and non clustered index, если вы сделали UUID pk то записи будут создаваться бардачно. nullable - это норм, какая разница менять валидацию в сервисах или nullable на true сделать? комментарии - а если это мне не удобно???????????????????????????? (говоришь как будто это всем удобно) не использования аннотации ManyToOne OneToMany и ManyToMany и тд, я согласен, без них жизнь становится все лучше и лучше. это единственный хороший совет а остальное это для веганов.
пишут что плюс минус одно и тоже, разница в несколько %, но это запросто кроется преимуществом в других операциях, когда тебе не нужно просить sequence в бд сгенерировать тебе автоинкрементное значение
@@konstantin.kozlovskiy в дефолтном btree индексе int инкрементится и добавляется практически без перестроек, с uuid необходимо переупорядочивать всё часто, перформанс у вас от этого разве не проседает?
"Приходит заказчик, и..." - интересный подход к разработке. А в перспективе это оправдывается? Например, Вы пробовали для сравнения более тщательное проектирование БД вместо того, чтобы идти на поводу у заказчика? Конечно, я не знаю специфики Вашего бизнеса, потому такие вопросы. Просто, пока вижу некоторые советы, здравые для максимально быстрой реакции на капризы заказчика, но не для максимально быстрой работы с БД (кроме "не держите открытыми транзакции"). Тогда зачем "HighLoad " в названии ролика?.. :/
Сколько тщательно не планируй базу, если меняется бизнес требование, разработка должна отреагировать, в процессе работы всегда встает вопрос "сделать гибко заранее предусмотрев капризы заказчика" или "сделать именно так как просит заказчик", от принятого решения может зависеть многое, бизнес зарабатывает для нас деньги, и мы не должны отказывать с позиции "мы тут технически придумали что будет вот так, а твоя хотелка не вписывается в наш профиль нагрузки". История с nullable полями нас никогда не подводила, и работать в таком режиме по итогу гораздо комфортнее. Технически это не несет никаких сложностей или проблем с производительностью, поэтому тут я не совсем понял про скорость. Мы работаем именно так, чтобы было максимально быстро.
Если перенос базы идет вместе с внутренними ключами - это плохая практика, внешние системы не должны знать о внутренних ключах. Для справки - существует такое понятие как альтернативный ключ или даже ключи состоящий - вот по ним при переносе и строится перенос данных и целостность в таблицах
UUID генерирует твоя программа, а автоинкрементное значение база данных при вставке, и все запросы "условно" встают в очередь пока один sequence выдаст nextval для тебя, это тоже работает быстро, но вот быстрее ли UUID'а - не уверен
@@konstantin.kozlovskiy это еще хуже! Так как внутренние ключи должна генерить БД - это бэст практис, твоя программа в отличном случае вообще не должна оперировать внутренними ключами БД
@@konstantin.kozlovskiy нет. бскорее просто какой-то невнятный договор программистов (архитекторов баз данных?) банально там далеко не всегда только s подставить: y - ies, f - ves. При том модели представляющие запись таблицы внезапно в единственном числе все. И банально это может очень много путаницы внести, особенно есди очередной джун не то чтобы хорошо знает инглиш
какая путаница может быть если модель называется Transaction.java а таблица transactions , а если слово плохо склоняется, добавляем *_entries, например summary_data_entries
Если вы используете БД в столь примитивном ключе - без FK без ENUMs без AK без default-ов - то вообще зачем вам реляционная БД ? И вообще постгрес зачем вам? Есть подозрение, что в вашей консерватории из яиц вылупились не куры а черти- что, как в известном рассказе Булгакова.
база ,в первую очередь, это база. даже в моей системе ,автоматизация общепита , используется не более 5-и ф-ций и-то из-за того что долбанный Честный Знак нужно в реал-тайме проверять .. а ну еще notify юзаю,чтоб разные системы не селектили по таймеру 1 и ту же таблицу (вернее селектили,но не ежесекундно :-) )
Извиняюсь за негативный комментарий заранее, но я пытался послушать до 51 секунды 4 раза и всё равно ничего не уловил. Голос очень монотонный, размеренный, медленный и сверх спокойный. Я бы его применил в приложении с звуками для засыпания
Давайте посмотрим правде в глаза: просто вы не умеете работать с внешними ключами) Как-то все умеют данные вставлять с ними, а вы почему-то не научились))) Очередная команда, которая уверена, что знает что-то лучше других, а по факту наоборот) Куча каких-то самопридуманных правил, не надо такое выдавать за истину и учить этому других людей
Я привел в видео достаточно аргументов за и против внешних ключей, сказал в каких ситуациях они нужны, а в каких ситуациях их использование невозможно. С этим бессмысленно спорить. Внешние ключи это инструмент, который нужно правильно использовать. Я лишь подсветил что для высоконагруженных систем и ентерпрайз специфики внешние ключи чаще всего не нужны. Если у вас свой проект или небольшая организация, пожалуйста, используйте внешние ключи.
Короче очень слабое видео) много спорных моментов. 11:40 last user modify и last time modify обычно как раз null по умолчанию, так как строка может быть ни разу не изменина
Комментарии везде и это удобно....Просто браво....Особенно это удобно в полях типа fio, name, surname, middlename, age, status и тд....Действительно, если бы не было комментариев, хер бы кто догадался что это такое!!!
Мои пять копеек:
- Присоединяюсь к тому, что 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 и КХД) должны ходить через сервис. Структура базы - никакая её часть - не должны становиться контрактом сервиса.
- Про триггеры в целом согласен, но с дополднением. Если уж надо что-то "типа триггеров", то посмотрите на материализованные представления. Но аккуратно.
Нет, пункты не кончились, просто писать надоело :)
Не знаю почему ютуб сразу не пропустил ваш комментарий, откопал одобрил, подписываюсь под каждым словом, прекрасно написано!
Вопросы. Большое спасибо за видео 1. Кто гененирует uuid записи, приложения или бд? 2. Кто дает гарантии что uuid будет уникальным или мы подагаемся на спецификацию (особенно актуально если нужно обьеденить данные из нескольких кластеров) 3. Как часто используюься сложные типы такие характерные для pg ( jsonb, hstore, array и т.д)
1. Приложение. 2. Сам UUID и дает гарантии, вероятность совпадения двух правильно сгенерированных UUID практически равна нулю (она настолько низка что тебе дадут премию если ты сгенерируешь коллизию. 3. Для хранения таких данных мы не используем PostgreSQL, тут лучше подойдет что-нибудь нереляционное вроде Redis
По первому я бы сказал база, но в редких случаях проще на стороне приложения сгенерировать и сразу в несколько других таблиц прокинуть uuid. Хотя, конечно, можно сначала запросить у базы, а потом уже прокидывать. Короч, зависит от задачи, но по дефолту на стороне базы
По поводу имен таблиц, есть еще мнение именовать в единственном числе, чтобы писать более читаемые запросы. Например 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-аннотаций. У меня на работе такое же мнение относительно этих штук.
когда набирают запросы руками, в 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
```
имба видос, столкнулся с такими же проблемами, давай еще про highload
Видео топ, почему так мало лайков(
Очень необычные, половинчатые советы. Например про UUIID id... Во первых, нет математической гарантии их неповторямости... и использовать UUID рационально только как дополнительное поле в качестве вторичного ID для связей между фрагментами распределенной БД... а для связей в локальных БД ясен перед должен использоваться быстрый и компактный INTEGER id... и самое главное для большой БД особенно на механических HDD - во избежание будущих тормозов на SELECT-запросах ни в коем случае нельзя создавать индексы по полям и функциям, которые участвуют в запросах и одинаковые значения которых равномерное распылены по всему объему записей - например по кодам операторов, по типам документов, по суммам платежей и т.д.
На моменте 10:35, с версии postgresql 11, добавление колонки с дефолт значением в существующую таблицу, не вызовет перезаписи значений по всех строках.
спасибо за ремарку, не знал, там оказывается теперь какая-то хитрая логика с постепенным назначением дефолтных значений, начиная с 11 версии.
Спасибо за видео, Кевин де Брейне
Шикарное видео
Если вы не используете increment, то что вы используете для справочных таблиц для pk, например "colors", "sizes", "types", "materials"? Естественный id или сгенерированный? Если естественный, то как вы называете? "id" или "name"
колонку называем id, с типом UUID
Спасибо за советы. Может у вас есть подходы к нормализации-денормализации бд раз вы не используете вторичные ключи и полагаетесь на уровень приложения? Сам много лет придерживаюсь подобных правил по неймингу, служебным полям и связям между сущностями. Также интересно узнать как вы проводите оптимизация работы вашего приложения в контексте базы данных. Спасибо за ответ :)
Денормализация строится в зависимости от бизнес процессов. Иногда, в угоду производительности выгодно дублировать данные в некоторых таблицах. Чаще всего мы добавляем элементы денормализации в виде ссылок между сущностями.
Например: транзакция имеет ссылку на услугу, услуга имеет ссылку на договор, договор имеет ссылку на филиал.
Бизнес процесс должен обработать все транзакции по филиалу Х.
Вместо того чтобы в момент работы бизнес процесса тянуть все 3 сущности из базы (замечу, что в микросервисной архитектуре эти сущности принадлежат разным сервисам и могут лежать в разных базах), мы (в момент обработки транзакции) проставляем на транзакции идентификатор филиала, для упрощения работы другим бизнес процессам.
Оптимизация базы данных через анализ отчетов pgbadger / анализ планов запросов / построение индексов / организацию работы с базой батчами
@@konstantin.kozlovskiy Спасибо за ответ. У себя на практике архитектурно использую подход Data vault в виде таблиц сущностей и словарей свойств плюс таблицы отношений, часто он приводит к денормализации, когда бизнес логика лежит в приложении.
Как вариант, можно приложения писать таким образом, чтобы при поиске строк не использовать первичный ключ, а другие уникальные поля с индексами, тогда не будет необходимости, чтобы один и тот же инстанс сущности имел одинаковые id в разных окружениях
Очень сложно избегать использования первичного ключа) это уже тогда смотреть в сторону альтернативных ключей, тут упоминали в комментах
Интересно услышать про текстовые поля. text / varchar / char. В каких случаях, какие использовать. Есть ли проблемы с ними
приведи примеры, использовать там где нужны строки, не использовать там где НЕ нужны строки, все просто ))
что text что varchar для PostgreSQL разницы нет, в последних версиях рекомендуют даже размерность не указывать
Привет! Интересное видео, возникло несколько вопросов:
1. Почему вы используете реляционную бд, а не nosql?
2. Какой либой вы получаете данные из бд?
3. Если вы не используете @Transactional, то не боитесь ли грязного чтения?
1. Мы не используем Nosql потому что нам нужна Sql база данных, реляционная. Я не понимаю почему вообще может возникнуть такой вопрос, у нас не документы, у нас транзакции и зависимые сущности которые мы раскладываем по таблицам и собираем в разном объеме при необходимости. Нет у нас JSON документов которые нужно складывать по ID и доставать по ID.
2. Мы используем Hibernate
3. Если мы не используем аннотацию @Transactional это не значит что у нас произойдет грязное чтение, через фреймворк любые изменения данных аля update / delete все равно пойдут в транзакции, аннотация будет стоять но на самом низко-уровневом методе репозитория а не где-то в слое бизнес логики. Грязных чтений не будет.
А если вам надо сделать операцию в БД, а потом логику и ещё раз операцию в БД. Если у вас транза в репо только то нет единой атомарности этих операций.
Убрали мы внешний индекс и для целостности данных нам нужно тогда сделать как минимум один select с limit 1 для проверки. Это оправданно к примеру когда данных на столько много что пересчет дерева занимает больше времени чем операция select. Но сразу несем потери из-за сетевых транзакций. Допустим две таблицы по 10 млн записей. И пришел к нам тот самый select перед вставкой. И так получилось, что нужное поле в самом конце. Postgres пробежит по всем строкам. Ок! Программист тогда понимает что это долго и берет индексирует поле. Ждет пол дня а потом радуется. Но тогда в чем был смысл вообще не ставить индексы в самом начале. На то они и реляционные базы данных что-бы иметь структуру данных. Есть другие базы типа Cassandra как раз для таких задач конечно там есть и свои нюансы. Потому всегда нужно выбирать компромис.
Внешний ключ проверяет целостность при любых манипуляциях с зависимыми данными. Если тебе нужно вставить 10млн записей в одну таблицу и 10 млн записей в другую, и они связаны. Без внешних ключей ты можешь делать это параллельно и с более высокой производительностью. Да есть риск - если у тебя в коде баг, возможно нарушение целостности. Но как я упоминал в видео, если ты связал две таблицы внешним ключом и решил их партиционировать (у нас же уже 10 млн и с каждым днем всё прибывает), то ничего не выйдет)
Итог "Чем проще - тем лучше"
Синтетический UID первичный ключ занимает прилично больше места по сравнению с автоинкрементным int.
И тянет за собой создание таких же по размеру полей-связей в других таблица.
Плюс строки более тяжелые при индексации.
В таких случаях индекс может занимать больше места чем сами данные таблицы.
Разве это оправданно во всех случаях ?
Эм, uuid - это два лонга (16 байт), ну или 4 инта. Не такая большая разница.
Другой вопрос, что индексируемое поле должно быть естественно сортируемым, а uuid v4 чистый рандом. Идеально подходит uuid v7, но в нескольких инстансах приложения непонятно будут ли коллизии
@@РоманГалиуллин-щ1п uuid4 это char(36) а это уже от 36 до 108 байт в зависимости от кодировки. Здесь разница уже на порядок по сравнению с int
@@АлександрМельниченко-ц8лоткуда у тебя чар36? Почитай про uuid, а не только буквы считай :)
Был бы он чар36, не было бы необходимости отдельный тип данных заводить под него
@@РоманГалиуллин-щ1п uuid это 36 символов по 1-3 байта (в зависимости от кодировки). А это уже на порядок больше int4
У нас огромные базы с миллиардами записей и партиционированными таблицами, сотни таблиц с ключами UUID'ами, никогда не испытывали проблем по объему, обычно данные занимают больше места в разы. Поэтому считаю что оправдано во всех случаях)
Со многим согласен, вопрос про внешние ключи. Мне не совсем понятен ваш концепт. Например есть таблицы users и transactions. Например в transactions уже 40 млн записей. В ней есть поле user_id, и когда я хочу получить последние 10 транзакций определённых пользователей я так или иначе буду их отсеивать по полю user_id, например соберу id нужных пользователей и буду проверять вхождение в этот массив. Ну и для этого я наброшу на поле user_id индекс, чтобы поиск был быстрым. И по сути это можно назвать внешним ключом. Точнее реализовать это можно как внешний ключ. Расскажите как вы реализуете подобные выборки, отдельно ли индекс создаёте в приложении?
индексы конечно нужны, но если у тебя две транзакционных таблицы, например transactions & transaction_data, и между ними есть связь, обе таблицы огромные и тебе необходимо их партиционировать, то между ними foreign ключей быть не может, а индексы конечно быть должны. если рассматривать таблицы users & transactions то вероятно таблицу пользователей ты захочешь шардировать и тут foreign ключи тоже тебе помешают. Такие ключи не доставляют неудобств только между таблицами справочниками, в которых не часто происходит вставка и которые не нужно шардировать или партиционировать
"Переливать данные между полигонами" типа скорпировать часть данных с прода на QA сервер?
как это технически делают? Как принято делать в больших конорах?
Данные с прода на тестовые полигоны не заливают, это нарушает десятки правил) с теста на прод бывает, настраивают сложный договор на тесте, проверяют и переносят. Также распространена миграция данных между разными тестовыми полигонами
@ ну зависит от данных наверное, личные данные может и не заливают.
Но рейты акций, почему нет?
@ если генерация синтетических данных для наполнения тестового контура невозможна, прибегают к процедуре обезличивания, этим занимаются целые команды, базу загружают в специальное место где каждую колонку с данными проверяют и подвергают обезличиванию с сохранением бизнес целостности, т.е если в базе ИНН организации встречается в нескольких местах, его обезличивают везде одинакого. Это очень сложная и кропотливая процедура.
А можно пару вопросов по внешним ключам?
Если не делать внешние ключи, то индексы тоже не делать под колонки, где должны были быть эти внешние ключи?
И как лучше получать единичную сложную сущность, которая раскидана по нескольким таблицам, если у нас нет внешних ключей?
Индексы нужны, если поле селективное. Отсутствие внешних ключей в бд не мешает вам собрать сущность джойнами или за несколько запросов
Индексы будут автоматически созданы для primary key второй таблице, которую хотите заджойнить, по крайней мере в Postgres.
Джойны работают не только с внешними ключами, а с любыми полями.
Он имеет ввиду ВНЕШНИЙ КЛЮЧ, когда говорит о вторичном ключе?
да
Во многих сценариях @OneToMany и тд. действительно не нужны, но иногда есть необходимость в сложным запросах (фильтрации данных по любым полям) к связанным таблицам . И тогда реализация подобных запросов вручную, приведет к написанию своего фреймворка, а будет ли он лучше?)
Мы избегаем сложных запросов, разными способами, в том числе иногда используется денормализация данных. Своего фреймворка не понадобилось до сих пор )
А почему нейминги boolean по типу IS_active считаете не лучшим вариантом?
Тоже удивился и не понял. Наоборот такой стиль позволяет лучше видеть, что там находиться в значении.
на самом деле это вкусовщина, просто когда будете мапить эти поля в java, геттеры будут генерироваться isActive(), а для других полей getXXX(), мне не нравится миксовать геттеры с припиской is
Вот только автор забыл сказать что uuid должен быть timebased uuid v7 иначе постгрес будет ребалансировать свое B-tree после каждой вставки, а лучше всего просто взять timebased long, что-то вроде snowflake ID который и место меньше займёт и выглядит нормально и работать будет быстрее классического uuid.
Шанс, что uuid совпадут минимален, но не равен 0😊
Полезное и интересное видео. Узнал для себя что-то новое. Константин, спасибо! 👍🤝
Интересно узнать еще о HighLoad правилах в рамках Ваших команд. В идеале какой-то github-репозиторий в котором собраны все эти правила в виде чеклиста)) 🙌
привет, спасибо, планирую еще ряд роликов на похожие темы, про обратную совместимость и фиче-тоглы )
@@konstantin.kozlovskiy про фича-тоглы очень классно было бы посмотреть!
Чет послушал: половина советов норм, половину на помойку. Но ситуация понятна, когда бизнес постоянно гонит делать быстрее, а ты тупо не успеваешь проанализировать ситуация и приходится говнокодить. В таких ситуациях это применимо. Но в действительно правильно структурированных организованных базах и проектах подобные советы просто идут нахер: учитесь работать с бд нормально, а не придумывать другие костыли, чтроб не сломались предыдущие
Да что ж это такое твориться... до коли.... когда ж на курсах и в университетах будут учить тому, что нужно в работе 😁😁
Твой инстинкт разработчика должен заставить тебя постичь всё самостоятельно :)
На моменте 6:51 - вторичный и внешний ключ вы считаете одним и тем же? Или сначала вы рассказываете про вторичный, а потом про внешний?
одним и тем же, просто их по-разному называют (foreign key)
@@konstantin.kozlovskiy foreign key - НЕ ВТОРИЧНЫЙ КЛЮЧ! Это внешний ключ обеспечивающий REFERENTIAL INTEGRITY - ссылочную целостность то бишь
То есть для каждого запроса нужно отдельную транзакцию? Или можно как-то работать с бд вообще без транзакций?
> То есть для каждого запроса нужно отдельную транзакцию?
SELECTы без транзакций, UPDATE/DELETE требуют транзакцию без вариантов.
> Или можно как-то работать с бд вообще без транзакций?
Вообще без транзакций - не думаю)
@@konstantin.kozlovskiy То есть проблема @Transactional в hibernate в том, что программист не задумывается, когда ставит аннотацию и транзакции создаются даже на select запросы?
@@konstantin.kozlovskiy UPDATE/DELETE всегда работает только внутри транзакции, даже если вы ее не намеренно не открыли
да, и закрываются гораздо позже чем этого желает разработчик, иногда фатально позже) а иногда думает что транзакция должна была открыться и ему будут доступны данные через аннотацию @Lazy , а обращение происходит из того же класса и магии не случается.
@@konstantin.kozlovskiy select тоже может использовать транзакции, причем неявные.
uuid значительно медленнее. Если Вы формируете сложные запросы int будет в десятки а иногда и сотни раз быстрее - например если запрос выполняется 20 секунд с uuid, то с int 0.2
такой разницы быть не может, вероятно поле с uuid'ом было в вашем случае без индекса, против int поля с индексом
@@konstantin.kozlovskiy либо Вы тестируете либо я, но я ленивый)))
нужно к примеру чтобы табличка скажем в мильён записей джоинилась сама с собой по инту и ууид
если у вас лапки дойдут до этого будет вам респект))) подпишусь и буду ждать)))
Код замеров в студию, пожалуйста. По моим ощущениям (и замерам 5-10 летней давности) - разница в производительности только в очень специфичных сценариях. Понятно, что всё что линейно зависит от объёма может стать медленнее: вставка, выборка миллионов записей, копирование в памяти на клиенте. Но это не очень большие затраты: в типичных OLTP системах типичная ширина записи 200-500 байт из которых 3-10 полей uuid. Если смотреть разницу между 64-битным int и 128-битным uuid, это будет 10-20% объёма. Ну ОК, именно эти поля обычно проиндексированы, возбьем 30%. Так что вот эти вот - достаточно органиченные - поерации станут дольше на 30%. Поиск по индексу (тем более по уникальному) будет практически как у int.
Если у вас такая большая разница, то рискну предположить, что где-то происходит неэффективная и лишняя конвертация uuid в строку и обратно.
как часто вы мигрируете данные? судя по советам, вы чуть не каждый день гоняете данные из одной бд в другую, и вам все мешает)
Довольно часто новые слуги и настройки переводов готовят на тесте и мигрируют в пром
Если не ошибаюсь некоторые бд даже не позволяют создавать вторичные ключи
А как работаете с транзакциями? Вручную все?
в spring boot jpa есть конструкция transactionTemplate - это если несколько операций необходимо выполнить в рамках одной транзакции, а update и delete , прямо в @Repository помечаем @Transactional, т.е. без аннотации на методе репозитория работать не будет, но в прикладной код эта аннотация не выносится
(У меня удаляется ответ)
Таблица "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, но почему все остальные варианты плохие?
pk - primary key должен быть в обоих таблицах (id) , в любом случае, мы считаем это как само собой разумеющееся
развязочная таблица многие ко многим нужна только если у вас по бизнес смыслу юзеры могут иметь несколько профилей, и один профиль может быть привязан к нескольким юзерам
п3. ошибочный, нельзя в таблице профилей сделать поле user_id pk, что-то здесь не так ) либо вы говорите про связь 1к1 либо имеете ввиду что-то другое
п4 не совсем понял
@@konstantin.kozlovskiy да. Связь 1к1? и еще вопрос. Вы сказали, что составных ключей не делаете. Даже для развязочных таблиц типа users_roles? Учитывая, что мы знаем, что там будут только users и roles, вы все равно делаете uuid для pk?
@@konstantin.kozlovskiy п4 - это создать для "profileInfo" для pk тип serial/identity columns, что бы просто id был и запрашивать "profileInfo" через "user_id"
@@konstantin.kozlovskiy п4. pk тип serial/identity для profileInfo, что бы был id и запрашивать profileInfo через user_id, т.к 1к1
@@konstantin.kozlovskiy я изменил п4 потому что ютуб опять удаляет коммент
Все четко и по делу! 👍👍👍👍
Вообще было бы неплохо перед тем как делать столь фривольное изложение манифеста про БД, узнать про сами БД чуть больше и глубже
В случаях когда фреймворк не поддерживает автоматическое заполнение служебных полей использование default вполне ок. Разумеется только в таких полях и в таких фреймворках. В остальных случаях default зло.
На счет связей ManyToOne, OneToMany, ты говори за что-то конкретное, а не в общем и целом, ибо есть фреймворки, которые не тянут связи, если ты не обратился к этой колонке, а возвращается просто пустая коллекция, как это сделано в доктрине. На счет enum, мол не юзайте, потому что вдруг в базу попадет значение которого в Enum нет и прога встанет. Может стоит на начать с того, а как они туда попадут, если изменения все делаются через фреймворки, а не напрямую в базе. Enum гарантирует наоборот, что в базу не попадет значение которого нет в Enum при добавлении или обновления базы через фреймворк
На счет идентификации, мол, не юзайте автоинкрмент, потому что данные невозможно будет потом мигрировать, во время миграции данных, нельзя опираться на идентификатор и идентификаторы не мигрируруются, мигрируются только основные данные
Иногда приходится менять данные в бд скриптами, когда аналитик на тестовом полигоне добавляет настройки какого-нибудь бизнес процесса и вписывает значение с русской буквой C вместо английской C в слове CONTRACT, а у тебя в приложении ENUM на колонку и из-за такого нелепого вмешательства твое приложение падает, это фиаско. По поводу миграции, в нашей ситуации бывает требуется настроить сложный договор силами команды разработки а затем смигрировать на другой полигон, в договоре десятки связанных сущностей, и для того чтобы обеспечить корректный "мердж" данных при последующих миграциях того же договора - без идентификаторов не обойтись
Ммм.. технологии джависта, использовать СУБД как хранилище.
FYI Аудит лучше делать на триггерах или stored procedures если доступ к базе через процедурный интерфейс
Конечно странная аргументация приведена по поводу вреда разметки связей. Есть же механизм Lazy и entity graph которые решают задачи подобные. Может просто надо ими уметь пользоваться разработчикам..
Если у нас есть таблица "profile_info". Можно ли для нее сделать primary Key в виде "user_id"? Или лучше serial в качестве PK, так как особо этот id и не нужен.
Не совсем понял что хранит таблица profile_info. Если она хранит профили пользователей, которые хранятся в таблице users, то в таблице profile_info не нужен user_id, скорее всего должен быть profile_id:uuid в таблице users (тут зависит от бизнес процессов). Я бы в обоих таблицах завел PK id:uuid. Уточните вопрос )
@@konstantin.kozlovskiy да. Таблица profile_info хранит доп информацию о пользователе. Зачем делать id для profile_info. Не проще ли запрашивать эту таблицу через "user_id", если она всегда будет одна для каждого пользователя? И сделать "user_id" в качестве pk. Просто зачем генерировать по миллиону ключей, отправлять эти id profile на клиент, тратить на это время, хоть и малое, если можно либо запрашивать через "user_id" и сделать его pk или сделать pk в виде serial. Смысла особо этот serial нести не будет, так как исп. "user_id"
@@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, которые не особо несут какого-то смысла, если можно запросить в качестве связи на прикладном уровне.
И я так понимаю, это тема связанна с нормальными формами. И вот еще вопрос: если ты следуешь нормальной форме определенной, то она должна распространяться на все таблицы?
@@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, но почему все остальные варианты плохие?
@@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, но почему все остальные варианты не очень?
Как ускорить запросы когда данных в таблице становиться несколько миллионов? Даже самы простые запросы выполняются по 10 секунд
Сделать шардинг и денормализацию как минимум
10 млн это мелочи, нужно посмотреть план запроса через EXPLAIN ANALYZE и вероятно ты увидишь sequence scan'ы, что покажет тебе вероятное отсутствие индекса на тех полях, которые используются в запросе
Если взять итог по поводу ИД: он должен не нести НИКАКОЙ смысловой нагрузки, потому что смыслы в жизни людей не постоянны. И должен быть максимально уникальный, не привязанный этой уникальностью тоже ни к чему. Просто точка уникальности.
Не побоюсь показаться глупым, ведь я действительно не очень силён в реляционных БД.
Не очень понял часть про неиспользование внешних ключей. Тогда каким образом БД понимает, какие сущности могут быть связанны с другими?
Например, как она соединит Useŕа и Tenant´а, если в пользователе не заложен ID этого самого Tenant´а?
PS: выглядит как вызов пояснительной бригады😅 Заранее благодарю!
Тут дело в "создании", как сущности в БД, а не поля, например, post_id в таблице users. Если я правильно понял
@@svitboomer8840благодарю!) Понадоедал своим коллегам, они мне объяснили, что при создании таблицы можно указывать FK, если этот ID куда-либо ссылается, либо не указывать, но здесь вся забота при соединении таблиц на тебе.
поля указывающие на идентификаторы других сущностей (зависимых) создавать конечно нужно, но не обязательно помечать их foreign key
@@konstantin.kozlovskiy спасибо большое за материал, было очень интересно и полезно!
Короче, я не понял почему вы не взяли mongo.
UUID пойдет только для малого количества проектов. UUID занимает больше места, проблемы с индексацией (index fragmentation) будет все хуже и хуже. рандомность uuid боком обернется при инсертах, индексы будут корявые а не по порядку. а ведь есть еще понятие clustered and non clustered index, если вы сделали UUID pk то записи будут создаваться бардачно.
nullable - это норм, какая разница менять валидацию в сервисах или nullable на true сделать?
комментарии - а если это мне не удобно???????????????????????????? (говоришь как будто это всем удобно)
не использования аннотации ManyToOne OneToMany и ManyToMany и тд, я согласен, без них жизнь становится все лучше и лучше. это единственный хороший совет а остальное это для веганов.
А поиск по uuid сравним с поиском по int?
пишут что плюс минус одно и тоже, разница в несколько %, но это запросто кроется преимуществом в других операциях, когда тебе не нужно просить sequence в бд сгенерировать тебе автоинкрементное значение
@@konstantin.kozlovskiy в дефолтном btree индексе int инкрементится и добавляется практически без перестроек, с uuid необходимо переупорядочивать всё часто, перформанс у вас от этого разве не проседает?
так далеко не копал, перфоманс в порядке на сотнях миллионов записей, запросы по индексам (по uuid полям) выполняются за миллисекунды
Есть уже uuid7
@@slashfast это согласен
"Приходит заказчик, и..." - интересный подход к разработке. А в перспективе это оправдывается? Например, Вы пробовали для сравнения более тщательное проектирование БД вместо того, чтобы идти на поводу у заказчика? Конечно, я не знаю специфики Вашего бизнеса, потому такие вопросы. Просто, пока вижу некоторые советы, здравые для максимально быстрой реакции на капризы заказчика, но не для максимально быстрой работы с БД (кроме "не держите открытыми транзакции"). Тогда зачем "HighLoad " в названии ролика?.. :/
Сколько тщательно не планируй базу, если меняется бизнес требование, разработка должна отреагировать, в процессе работы всегда встает вопрос "сделать гибко заранее предусмотрев капризы заказчика" или "сделать именно так как просит заказчик", от принятого решения может зависеть многое, бизнес зарабатывает для нас деньги, и мы не должны отказывать с позиции "мы тут технически придумали что будет вот так, а твоя хотелка не вписывается в наш профиль нагрузки". История с nullable полями нас никогда не подводила, и работать в таком режиме по итогу гораздо комфортнее. Технически это не несет никаких сложностей или проблем с производительностью, поэтому тут я не совсем понял про скорость. Мы работаем именно так, чтобы было максимально быстро.
В общем попросили не ругать за правила для работы с жутким легаси с отсутствующей организацией структуры данных. окей.
Откажитесь от фреймворка, чего уж мелочиться
Он все же полезен)
Если перенос базы идет вместе с внутренними ключами - это плохая практика, внешние системы не должны знать о внутренних ключах. Для справки - существует такое понятие как альтернативный ключ или даже ключи состоящий - вот по ним при переносе и строится перенос данных и целостность в таблицах
Что за внутренние ключи?
генерация UUID требует гооораздо большего времени чем автоинкремента - если что вдруг про хайлоад речь зашла тут
UUID генерирует твоя программа, а автоинкрементное значение база данных при вставке, и все запросы "условно" встают в очередь пока один sequence выдаст nextval для тебя, это тоже работает быстро, но вот быстрее ли UUID'а - не уверен
@@konstantin.kozlovskiy это еще хуже! Так как внутренние ключи должна генерить БД - это бэст практис, твоя программа в отличном случае вообще не должна оперировать внутренними ключами БД
нейминг: я бы поспорил только в одном моменте - множественное число.
тут да, вкусовщина
@@konstantin.kozlovskiy нет. бскорее просто какой-то невнятный договор программистов (архитекторов баз данных?)
банально там далеко не всегда только s подставить: y - ies, f - ves. При том модели представляющие запись таблицы внезапно в единственном числе все. И банально это может очень много путаницы внести, особенно есди очередной джун не то чтобы хорошо знает инглиш
А person во множественном числе и вовсе будет people. ))
какая путаница может быть если модель называется Transaction.java а таблица transactions , а если слово плохо склоняется, добавляем *_entries, например summary_data_entries
@@konstantin.kozlovskiy и нахуя больше правил и исключений? Кому оно надо?
Если вы используете БД в столь примитивном ключе - без FK без ENUMs без AK без default-ов - то вообще зачем вам реляционная БД ? И вообще постгрес зачем вам? Есть подозрение, что в вашей консерватории из яиц вылупились не куры а черти- что, как в известном рассказе Булгакова.
ACID как никак на дороге не валяется )
@@konstantin.kozlovskiy кроме как в постгресе ACID нигде не присутсвует?
вот тоже слушал и думал что парни страдают делая nosql из sql
база ,в первую очередь, это база. даже в моей системе ,автоматизация общепита , используется не более 5-и ф-ций и-то из-за того что долбанный Честный Знак нужно в реал-тайме проверять .. а ну еще notify юзаю,чтоб разные системы не селектили по таймеру 1 и ту же таблицу (вернее селектили,но не ежесекундно :-) )
если в таблице "миллиард" записей - то это плохая история дла постгреса и тут стоит думать про какй-нть другой движок БД
нормальная история с учетом того, что таблица партиционирована, кто запретил хранить в базе много записей?)
А чем именно плоха эта история?
Извиняюсь за негативный комментарий заранее, но я пытался послушать до 51 секунды 4 раза и всё равно ничего не уловил. Голос очень монотонный, размеренный, медленный и сверх спокойный. Я бы его применил в приложении с звуками для засыпания
Не ВТОРИЧНЫЕ клбчи а ВНЕШНИЕ КЛЮЧИ FOREIGH KEYS то бишь !!!!!! Дядя!
Я даже гуглил, что такое вторичные ключи. Пока гуглил, речь пошла про foreign keys, и гугление прекратилось )
Это "вредные советы", почти все...
Давайте посмотрим правде в глаза: просто вы не умеете работать с внешними ключами) Как-то все умеют данные вставлять с ними, а вы почему-то не научились))) Очередная команда, которая уверена, что знает что-то лучше других, а по факту наоборот) Куча каких-то самопридуманных правил, не надо такое выдавать за истину и учить этому других людей
Я привел в видео достаточно аргументов за и против внешних ключей, сказал в каких ситуациях они нужны, а в каких ситуациях их использование невозможно. С этим бессмысленно спорить. Внешние ключи это инструмент, который нужно правильно использовать. Я лишь подсветил что для высоконагруженных систем и ентерпрайз специфики внешние ключи чаще всего не нужны. Если у вас свой проект или небольшая организация, пожалуйста, используйте внешние ключи.
Короче очень слабое видео) много спорных моментов.
11:40 last user modify и last time modify обычно как раз null по умолчанию, так как строка может быть ни разу не изменина
last modify = create при создании записи