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 (не відображається)