Comments 16
Все же это не масштабируемый ID, который может стать узким местом. Это его основной недостаток.
Кроме того, надо понять насколько хорошо это будет работать в транзакциях. На мой взгяд, UUID или Time-based UUID все же лучше.
А в MariaDB можно просто написать CREATE SEQUENCE
и не изобретать велосипед.
https://mariadb.com/kb/en/create-sequence/
А как потом по Id узнать, в какой таблице экземпляр сущности? Делать выборку из всех с union?
Да, можно через union. Но если такая задача стоит на постоянной основе, а не в рамках дебага сложного кейса, то стоит иметь таблицу связи: тип сущности и id. Но с uuid будут те же проблемы.
Вообще я редко сталкивался с необходимостью определить, откуда взялся id ибо если REST "/user/123/purchase/567", то довольно просто понять в какие таблицы смотреть.
Если и так понятно, в какие таблицы смотреть, тогда зачем шарить множество ID по таблицам? Значит, все-таки существует кейз, когда непонятно? И что мешает, например, запустить identity-генератор в первой таблице с 1, во второй - скажем с миллиарда, а в третьей - с двух?
Если и так понятно, в какие таблицы смотреть, тогда зачем шарить множество ID по таблицам?
Например, реальный кейс есть urls: /schedule_task/:id, /manual_task/:id, /osa_task/:id
. Как написано в статье у них разный workflow, но при этом они в пользовательском приложении находятся в одном разделе. Например, достаточно дернуть url c неверным id, например, вместо /schedule_task/1 /osa_task/1
и мы получим битые данные(для sql запросов аналогично). С sequence такое провернуть нельзя.
Значит, все-таки существует кейз, когда непонятно?
Да, был кейс в legacy системе, в которой был один sequence вообще на все таблицы в БД, а вместо REST был RPC с невнятным наименование методов. Но как правило в бизнес-логике мы работаем с конкретной таблицей/таблицами и нам не особо требуется выяснять откуда взялся id из sequence или auto_increment.
И что мешает, например, запустить identity-генератор в первой таблице с 1, во второй - скажем с миллиарда, а в третьей - с двух?
Да, вообщем-то ничего, просто sequence поудобнее. Ведь придется диапазоны подгадывать или менять их при добавлении новых таблиц, плюс не все id будут заюзаны - в одной таблице 100 000 сущностей и больше не добавляются, а второй и миллиарда не хватит. Плюс можно sequence зацикливать.
Как уже выше написали про "Class Table Inheritance" -- отработанный вариант, который любая ОРМка поддерживает. В предложенном вами варианте проглядывается велосипед и программирование на стороне БД.
Факт программирования на стороне БД в отрыве от контекста задачи не может быть отрицательным ярлыком. Есть огромное множество задач, в которых оно исключительно полезно для эффективности и целостности данных. Существование и, главное, развитие средств внутрибазного программирования по мере увеличения номера версии БД-движка является доказательством их необходимости.
По поводу же велосипедов - тот же паттерн clas table inheritance при массовой вставке записей скорее всего будет нереализуем ни средствами ORM (она не догадается смапить множество новых ID), ни данным решением.(last_identity + 1 не даст гарантии на уникальный новый ID, если сиквенс параллельно дернет кто-то еще)
Существование и, главное, развитие средств внутрибазного программирования по мере увеличения номера версии БД-движка является доказательством их необходимости
Это скорее следствие легаси, но это не значит, что это хорошо. СУБД прежде всего query, а уже во вторую очередь language. Но это холивар.
скорее всего будет нереализуем ни средствами ORM
Тогда это не ОРМ. Изучите как оно работает. Внутри транзакции в табличке документов будет хоть 100500 автоинкрементов генериться, даже если паралельно это ещё 100 потоков делает. Единственно они не будут друг за другом. Но уникальность будет.
"Это скорее следствие легаси" - нет, к счастью. Если бы это было только необходимостью поддержания легаси, то множество новых фич, доступных только из-под кода БД, стремилось бы к нулю. Поскольку это не так, значит, существует способ работы с БД, отличный от ORM, для которого эти фичи разрабатываются. А поскольку разработка фичей стоит денег (и немалых), то за счет этих фичей производитель движка БД надеется получить конкурентное преимущество перед теми, у кого их нет. А раз эти фичи невозможно использовать посредством ORM, следовательно существует альтернативный способ работы с данными, используя эти фичи. Альтернативный ORM способ - это inner-base programmability (IBP).
"Изучите, как оно работает" - спасибо, я в курсе "thread-safe" поведения секвенсеров и identity :). Я имею в виду операции типа массовой вставки и получения обратно множества ключей, чтобы потом по ним связать и иерархические сущности. Эта задача нетривиальна даже для внутрибазного программирования, и скорее всего не реализована ни в одной ORM.
Говоря в более общем виде, в отличие от средств IBP, ORM не обладает полным информационным контекстом, и поэтому не может быть эффективна там, где обладание им критично по бизнес-логике или по эффективности (например, когерентные массовые апдейты)
Но получается, что у нас в таблице
sequence
под каждый выданный id имеется запись. Не очень красиво, хотя и не критично... Хотелось бы просто иметь одну запись, в которой указан последний выданный id.
Что мешает в том же триггере удалить из таблицы все записи (а по факту почти всегда - одну запись), в которых id меньше свежесгенерённого?
BEGIN
INSERT INTO sequence SET id=DEFAULT;
SET NEW.id = LAST_INSERT_ID();
DELETE FROM sequence WHERE id < NEW.id;
END
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=49ca20f3f01eb27d52c127beda86bd4c
То, что у разных задач разный маршрут в "документообороте" и разный набор атрибутов, отнюдь не запрещает им быть в одной таблице. Ну это так, к слову.
А вот "иметь одну запись, в которой указан последний выданный id " - проверенный практикой рациональный вариант. Он приближает вас к решению задачи обеспечения уникального сиквенса в рамках распределенной по нескольким серверам системы (с возможностью автономной работы/с не гарантированно работающими каналами связи).
Ну и если забыть про существование триггеров и тому подобных измышлений больного ума, станете ближе к кроссплатформенности в смысле СУБД.
uuid плохо влияет на работу primary key и не очень хорошо дружит с Btree индексом
Хранить uuid как char — это ещё догадаться надо. В MySQL 8+:
UUID_TO_BIN(string_uuid), UUID_TO_BIN(string_uuid, swap_flag)
If swap_flag is 1, the format of the return value differs: The time-low and time-high parts (the first and third groups of hexadecimal digits, respectively) are swapped. This moves the more rapidly varying part to the right and can improve indexing efficiency if the result is stored in an indexed column.
что решает проблему. Для старых версий можно написать идентичные функции в виде хранимок.
Ни разу в жизни не нужно было делать как написано в пункте {Подготовка}. А вот один ид для многих таблиц постоянно требуется. По сути, описанное вымышленная задача не имеющая практического применения.
Можете минусовать, но переубедить не получится.
Всё уже давно придумано 10 лет назад https://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/
Используем sequence в MySQL