MySQL. UUID-short. JavaScript.
MySQL
Использование баз данных (БД) в современной разработке программного обеспечения (ПО) является, де-факто, архитектурной необходимостью. За исключением отдельного ПО особого назначения, процессы образования, хранения и обработки данных традиционно возлагаются именно на системы управления базами данных (СУБД). Развитие разных СУБД во многом регулируется маркетинговыми средствами, например, реализацией исключительных особенностей, отсутствующих у СУБД конкурентов. Отличительными чертами разных СУБД являются, в частности, типы данных, которые обеспечивают хранение постоянной информации, уникальные команды и функции. Эти особенности часто приводят к определённой несовместимости разных СУБД между собой. Поэтому, прежде всего, следует определиться с тем, какая СУБД будет в нашем проекте и какой язык программирования мы будем при этом использовать. Наш выбор остановился на одной из наиболее популярных СУБД - MySQL. Языком программирования был выбран JavaScript (в варианте Node JS).
При настройке взаимодействия ПО с СУБД следует обратить внимание на то, что типы данных языка программирования должны быть достаточными для трансфера данных с БД без потерь. Причём указанный вопрос должен касаться именно той СУБД и того языка программирования, которые используются в проекте. При смене СУБД или языка вопрос должен пересматриваться. Существует очень потешный пример: в большинстве БД тип данных FLOAT отвечает за действительные числа и имеет размер 64 бита. В то же время, значительное количество языков программирования (С, C++, C# и т.п.) предусматривают для типа FLOAT 32 бита. Очевидно, что при попытке передать данные с БД в программу возникает ошибка согласования. Но её текст образца "Невозможно передать FLOAT данные во FLOAT переменную" выглядит шокирующе. Все программисты помнят, что типу FLOAT в БД соответствует тип DOUBLE в языках программирования.
Похожие ситуации время от времени возникают при частой работе программистов с базами данных. Одной из таких ситуаций мы хотели бы поделиться в рамках данной статьи. Забегая вперёд, немного раскроем интригу. В языке программирования JavaScript для представления чисел предусматривается 64 бита, но не все из них отвечают за само число. Это может стать проблемой при трансфере 64-битных данных с БД. Как известно из определённых законов, если такое может стать проблемой, то это происходит. И, конечно, произошло...
Для того, чтобы почувствовать это на себе, предлагаем пройтись по нашему пути. Включите
консоль СУБД (естественно, подойдёт Workbench или phpmyadmin). Создайте таблицу
для испытаний: CREATE TABLE `uniques`( `guid` BIGINT )
UUID-short
Одним из фундаментальных принципов конструирования БД является обеспечение идентификации данных. Достаточно часто мы видим, особенно в учебниках, использование автоматического инкремента для ключевых полей, вследствие чего они являются обычными счётчиками. На самом деле это не очень хорошая практика. Во-первых, практически во всех таблицах появляется запись с id=1. Это немного ухудшает понимание самого слова "идентификатор" как чего-то уникального, неповторимого. А тут оказывается, что есть повторы. Да ещё и в каждой таблице. Во-вторых, возникает бесконечный источник ошибок типа "copy-paste" (слышали о таком приёме программирования?). Если забыть исправить условия связывания таблиц, то неправильный запрос окажется частично рабочим, но для тех id, значения которых одинаковы в разных таблицах. Выявить такие ошибки достаточно сложно, особенно, когда в тестовых таблицах всего по 3-4 записи и все с одинаковыми id (1-2-3-4). В-третьих, счётчики имеют сниженные показатели безопасности. Если вам нужно "угадать" идентификаторы пользователей и на одном из примеров вы видите "user-id=42". Можно прогнозировать, что вы угадаете все наявные идентификаторы пользователей.
Современный стандарт ISO/IEC 9834-8 (TU-T Rec. X.667) рекомендует использование глобально уникальных идентификаторов (UUID, GUID), которые исключают описанное выше совпадение значений идентификаторов в разных таблицах. Более того, во всём мире не будет двух записей с одинаковыми идентификаторами (если правильно их использовать, конечно), для этого сделан огромный запас в 128 бит. Средства СУБД MariaDB позволяют генерировать как полный UUID (128 бит), так и его сокращённую версию "uuid_short" (64 бит). Последнее можно считать хорошим компромиссом, поскольку высокое быстродействие, характерное для целочисленной арифметики, объединяется с принципом глобальной уникальности. Дополнительной позитивной чертой является то, что преобладающее большинство языков программирования поддерживает работу с целочисленными данными разрядностью 64 бита, а с 128 битами - не всегда. Комбинаторная мощность множества с информационной энтропией в 64 бита составляет 16 эксабайт и является полностью достаточной для большой БД, хотя и не претендует на мировую глобальность, которую способна обеспечить 128-битная идентификация. Но в пределах одной СУБД идентификаторы повторяться не будут.
Для того, чтобы оптимально использовать ёмкость идентификаторов, генератор
"uuid_short" сразу создаёт большие числа, начиная с разрядности 56 бит.
Убедимся в этом и внесём в нашу таблицу несколько данных. Запишите
команду в БД: INSERT INTO `uniques` ( `guid` ) VALUES ( UUID_SHORT() )
Выполните эту команду несколько раз. Затем просмотрите содержание таблицы "uniques"
с помощью команды SELECT `guid` FROM `uniques` или способами
вашего инструмента. В нашей таблице были следующие данные:
99602537291710468, 99602537291710469, 99602537291710470, 99602537291710471
JavaScript
Согласно документации, JavaScript обеспечивает работу с числами разрядностью 64 бита, однако, отдельные комбинации выделены для специальных нужд (см. разд. 6.1.6.1), например, NaN или Infinity. В то же время, СУБД не накладывает дополнительных ограничений на значения чисел, но и не берёт граничные значения (как мы увидели выше - примерно 56 бит). Как следствие, нужно провести дополнительные исследования совместимости данных с разрядностью 64 бита между языком JavaScript и СУБД.
Для работы с СУБД в JavaScript устанавливаются дополнительные модули (пакеты). Наиболее распространёнными для СУБД MySQL являются пакеты «mysql» и «mysql2». На момент наших экспериментов эти пакеты выступали основой для примерно 10 тысяч других пакетов (смотрите вкладку "Dependents" в приведённых ссылках). Большинство информационных источников по JavaScript рекомендуют указанные пакеты как основные для работы с СУБД. Соответственно, полученные выводы косвенным образом будут касаться и их. Пакеты «mysql» и «mysql2» предоставляют практически одинаковую функциональность, в рабочих проектах используется лишь один из них. Вибор двух пакетов сделан исключительно для сравнительного анализа их трансферных возможностей.
Что ж, от слов к делу: создадим проект на NodeJS, в нём один файл, который выполняет
запрос, приведённый выше, и выводит результат на консоль:
Мы увидели на экране следующие данные:
const mysql2 = require( 'mysql2' ) ;
const con = mysql2.createConnection( ... ) ; // your connection data
con.connect() ;
connection.query(
"SELECT `guid` FROM `uniques` ",
( err, data ) => {
for( let row of data ) {
console.log( row[ 'guid' ] ) ;
}
});
99602537291710460, 99602537291710460, 99602537291710460, 99602537291710460.
Как видно, числовые данные искажаются – все они округляются до одинакового значения
«99 602 537 291 710 460» (для наглядности между разрядами добавлены пробелы).
Что это было? Это проявили себя особенности представления больших чисел. В JavaScript - неизменное представление гарантируется лишь для значений, которые не превышают величины 253. А у нас числа имеют бинарный порядок ~ 256. Вот и произошла трансформация. С одной стороны, всё сказанное полностью логично, однако, ни модули «mysql» (или «mysql2»), ни сам JavaScript не сделали замечания, не дали подсказок, не выдали ошибок. А это исключительная ситуация - данные передаются с искажением, - что это, если не ошибка?!
Как это исправить? Проще всего перейти на строчное представление чисел. Тоесть извлекать их из БД не как числа, а как строки. Поскольку идентификаторы обычно не принимают участия в арифметических операциях, а лишь сравниваются, это не изменит работу программы. Тоесть ничего сложного. Смутило то, что ошибка была разрешена системой и выявили мы её почти случайно, из-за того, что начались странные дублирования данных на сайте. Такими ситуациями всегда хочется поделиться.
Оставить комментарий
Имя (отображается)E-mail (не отображается)