Моят личен и професионален живот

2019-01-31

Моите MySQL бъгове

Откакто започнах да използвам MySQL 3.23.x пред годините ми в университета се опитвах да решавам проблемите си сам както с другия свободен софтуер и софтуер с отворен код, който използвах. Първоначално беше повече обучение (или липса на знание ако щете), но след това проблемите ставаха по-сложни и трудни за решаване. Е, успявах повечето пъти, така че не вземах в предвид докладване на бъгове или търсене на поддръжка. Обаче еко системата на  MySQL порасна от тогава, софтуера стана по-сложен и базата с изходен код се увеличи значително, така че днес е наивно да се смята, че човек може да оправи всички проблеми, които среща без да се задълбава в вътрешните елементи на продукта или разчита на професионална поддръжка. Първото е възможно само за MySQL разработчици добре запознати с базата с код и големи организации като Facebook, Booking, и т.н., които имат специализирани екипи и редовно допринасят кръпки. Второто зависи от вас и разбира се компанията предоставяща поддръжка.

Не съм докладвал много бъгове за MySQL - само 21, но вече смятам да докладвам всичко което открия, което изглежда като бъг, защото това е правилното нещо. Може и да се справя с проблема сам, но някой може да се натъкне на същото и трябва поне да е наясно, че това е познат (и евентуално вече решен) проблем. С всеки един от тези научих по нещо малко, така че тук отдолу правя преглед на бъговете си до сега описвайки тяхната съдба в хронологичен ред.
  1. Бъг 20098 (първият ми) беше докладван на 2006-05-26 и е със състояние "No Feedback" от 2006-07-01. Беше за Query browser търсещ файл, който не съществува (preferences.glade). Бях забравил за този и открих, че е затворен по-късно, защото не съм предоставил обратна връзка, което е забавно, защото бях предоставил поисканата информация...
  2. Бъг 69459 беше докладван на 2013-06-13 и е в състояние "Can't repeat" от 2013-12-24. Беше за MySQL Workbench сриващ се при въвеждане на точка в SQL низ когато е разрешено автоматично довършване на кода. Докладвах бъга за версия 5.2.47 CE, но бях посъветван да пробвам 6.0.7, която работеше, така че бъга не получи повече внимание тъй като 5.2 серията не получи повече обновления.
  3. Бъг 73076 (първият ми потвърден и оправен) беше докладван на 2014-06-22 и е със състояние "Closed" от 2014-08-26. Беше за MySQL Workbench всъщност записваш промените в набор с редове когато само са приложени. Беше гаден бъг правещ ежедневната ми работа с Workbench трудна, тъй като трябваше да внимавам да не приложа (извинявам се запиша) твърде рано. Проблема се появи в 6.0.x, 6.1.x и беше оправен в крайна сметка с пускането на 6.2.2 на 2014-09-05 (виж бележки към версия) след като беше потвърден от поддръжката.
  4. Бъг 73079 беше докладван на 2014-06-23 и е в състояние "Closed" от 2014-09-15. Беше за MySQL Workbench, който не опресняваше клетката след задаване на стойността на NULL. Засягаше 6.1.x и ранните 6.2.x версии. Въпреки, че не беше официално потвърдено, за мен това беше регресия, но по-важното е, че беше оправен с пускането на 6.2.3 на 2014-09-23 (виж бележки към версия).
  5. Бъг 73708 беше докладван на 2014-08-25 и е със състояние "Closed" от 2014-12-01. Беше за MySQL Workbench моделите, които бяха напълно разбъркани. Забравих този за няколко месеца и предоставих поисканата обратна връзка после, за да разбера, че проблема е вече оправен с пускането на  6.2.4 на 2014-11-20, въпреки че нищо не беше споменато в бележките към версия.
  6. Бъг 73770 (най-стария ми все още отворен) беше докладван на 2014-08-29 и все още е в състояние "Verified" от 2018-02-05. Представлява заявка за нова функционалност в MySQL синхронизатора между модел и база с данни да показва действителните разлики. Който е ползвал тази възможност би трябвало да знае, че прилагането на промените от модел в базата с данни може да бъде доста досадно, защото винаги има някакви неочаквани промени и още повече такива, които не можеш да разбереш. Виждаш само скрипта за обновяване, но ще бъде хубаво да се виждат действителните разлики, което в някои случаи може въобще да не са разлики (виж 90772 по-долу).
  7. Бъг 82202 беше докладван на 2016-07-12 и е със състояние "Closed" от 2018-01-31. Беше за невъзможност за свързване на Connector/ODBC 5.3.6 към споделената библиотека libmysqlclient.so заради неопределени символи (напр. my_malloc, my_free, и т.н.). Проблема беше бързо обяснен, но след това затворен с "Not A Bug". Беше отворен отново след като Fedora отговорник докладва същия проблем и в крайна сметка оправен с пускането на Connector/ODBC 5.3.10 на 2018-01-30 (виж бележки към версия).
  8. Бъг 84951 (първата ми предложена кръпка) беше докладван на 2017-02-10 и е със състояние "Duplicate" от 2017-02-12. Беше за проблем с изграждането на MySQL Workbench 6.3.9, заради компилационни грешки в jsonparser.cpp и jsonview.cpp, за които предложих кръпка. Бъга беше направен двойник на 84886, който също съдържаше кръпка предоставена два дена по-рано, но беше затворен на 2018-05-14 като "Won't fix", защото те "вече не поддържат 32 битови системи" (!?).
  9. Бъг 89608 беше докладван на 2018-02-09 и е все още в състояние "Verified" от 2018-02-13. Това е заявка за нова функционалност за това съобщенията за изискванията към паролите в MySQL Workbench да отговарят на конфигурацията на сървъра, така че да са по-полезни. Предоставих примерен код за това как може да бъде направено и дори написах SQL функция за да пробвам идеята.
  10. Бъг 89615 беше докладван на 2018-02-10 и е със състояние "Unsupported" от 2018-08-23. Беше за неуспех с изграждането на MySQL Shell 1.0.10, заради пропадаща компилация за неопределени vio* функции. Беше свързан с 82202 и въпреки че беше потвърден по-късно беше затворен с обяснението, че "MySQL Shell 1.0 вече не се поддържа и трябва да се ползва 8.0 вместо това" (!?) на което реагирах малко грубо, но наистина не разбирам такива отговори.
  11. Бъг 90619 беше докладван на 2018-04-25 и е със състояние "Duplicate" на 79315 от 2018-04-25. Беше за това, че MySQL Installer не предлага надграждане от MySQL 5.7 до 8.0, което беше обяснено като "не е бъг", защото "работи според очакванията", така че явно MySQL Installer не поддържа надграждания между главни версии. Надявам се това да се промени в бъдеще.
  12. Бъг 90620 (първият ми от серия) беше докладван на 2018-04-25 и е все още в състояние "Verified" от 2018-04-26. Този е за MySQL Workbench 8.0.11's SQL редактора, който показва грешка на SELECT заявка с прозоречни функции. Не бихте очаквали MySQL Workbench имащ същата версия като сървъра да не поддържа новите възможности на сървъра, но повече за това по-късно.
  13. Бъг 90727 беше докладван на 2018-05-03 и е със състояние "Closed" от 2018-06-01. Беше за невъзможност да се свърже Connector/C++ 1.1.11 поради липсващи mysql_sys и mysql_strings библиотеки. Оказа се регресия от някакви специфични за Solaris промени, които не са направени Solaris специфични. Свързах се с разработчика и беше обявено за оправено в 1.1.12, която излезе на 2019-01-28 (виж бележки към версия), така че за 1.1.11 трябваше да закърпя сам.
  14. Бъг 90772 беше докладван на 2018-05-06 (St George's Day) и е все още в състояние "Verified" от 2018-05-09. Беше за MySQL Workbench синхронизатора правещ разлика между варианти за избягване на единични кавички (напр. израза COMMENT 'Currency\' symbol' е различен от израза COMMENT 'Currency''s symbol'). Това е защото MySQL Workbench позволява избягване с наклонена черта и сървъра го приема за изпълнение, но след това го пренаписва като избягване с единична кавичка. Така, при следващата синхронизация разликата остава (т.е. все едно не сте синхронизирали въобще).
  15. Бъг 90876 беше докладван на 2018-05-15 и е със състояние "Closed" от 2018-06-21. Беше за MySQL Shell 8.0.11 даващ грешка 5115 при добавяне на документи към колекция в 5.7 сървър, но това за което беше всъщност е автоматично създавани идентификатори както обясних в подробности в статията ми MySQL Shell и автоматичното създаване на идентификтори за документи. Този предизвика обновяване на документацията и тъй като никой друг нямаше този проблем предполагам, че е добре, но за мен такива проблеми чупят обратната съместимост.
  16. Бъг 91841 беше докладван на 2018-07-31 и е със състояние "Closed" от 2019-01-25. Беше а невъзможност да се изгради MySQL Connector/ODBC 5.3.11, заради компилационни грешки. Успях да оправя компилационните грешки сам (виж кръпка) докато чаках за помощ и обяснение. А обяснението беше, че е двойник на вътрешен бъг, "който обяснява причините за неуспеха", но който не мога да прочета. Забавното нещо в това беше, че имаше повече от 400 реда изтрити от началото на заглавен файл най-вероятно свързано с обновявания на текста с авторското право. Беше оправен с пускането на 5.3.12 на 2019-01-28 (виж бележки към версия).
  17. Бъг 92898 беше докладван на 2018-10-23 и е със състояние "Not a Bug" от 2018-10-23. Смятам този за единствената ми "заявка за поддръжка" и бях доволен, че беше обяснен същия ден.  Беше за това, че ударих несъвместима промяна при надграждане от 8.0.12 до 8.0.13, защото все още имах някои стари изгледи използващи ASC или DESC квалификатори с GROUP BY клауза. В тази връзка искам да благодаря на MySQL подробните бележки към версия, които публикуват, защото в този случай дори да ги четеш внимателно можеш да изпуснеш нещо смятайки, че не те засяга. Поради тази причина е силно препоръчително да се ползва Upgrade Checker Utility на MySQL Shell, който може да ви кажеш за грешки със съвместимостта и проблеми предварително.
  18. Бъг 92900 беше докладван на 2018-10-23 и е в състояние "Verified" от 2018-10-23. Този е за това, че в MySQL Workbench 8.0.13 липсва поддръжка за изрази в DEFAULT, въпреки че функционалността се поддържа от същата версия на сървъра.
  19. Бъг 92908 беше докладван на 2018-10-23 и е в състояние "Verified" от 2018-10-23. Този е за това, че в MySQL Workbench 8.0.13 липсва поддръжка за изрази като части от ключ, въпреки че функционалността се поддържа от същата версия на сървъра.
  20. Бъг 93835 беше докладван на 2019-01-07 и е в състояние "Verified" от 2019-01-10. Това е заявка за нова функционалност за това MySQL Workbench да показва предупреждение при използване на ключова дума като идентификатор (напр. имена на таблици и колони). Отворих го след като Frédéric Descamps предложи това в менение изразено в LinkedIn. Стискам палци да бъде направено.
  21. Бъг 94012 беше докладван на 2019-01-23 и е в състояние "Verified" от 2019-01-23. Този е за това, че MySQL Workbench 8.0.14 не разпознава новата ключова дума LATERAL и дава грешка след нея в SQL редактора. За сега това е последния от серията ми за това, че MySQL Workbench не поддържа възможностите на сървъра, въпреки че са със същата версия. Бъга беше номиниран като MySQL Бъг на деня от Valerii Kravchuk на същия ден.

Надявам се не съм ви отегчил с цялата тази информация отгоре. Не винаги е лесно правилно да се обясни един проблем, но аз имам дост опит бидейки от "другата страна", така че смятам, че знам каква информация е необходима, за да може един проблем да бъде изследван старателно. Все пак докладването на бъгове вероятно е изкуство и очаквам представянето на Valerii Kravchuk на тема How to create a useful MySQL bug report на FOSDEM в Събота.

Някои от бъговете ми са чисто технически (напр. 82202, 84951, 89615, 90727 и 91841) за които съм докладвал всички подробности и все пак обясненията на някои от тях ми изглеждат твърде странни (напр. все не се поддържат 32 битови системи, MySQL Shell 1.0 вече не се поддържа и версия 8.0 трябва да се използва вместо това), въпреки че проблема и това което трябва да се оправи стана доста ясно. Не разбирам как може да не се пуска нова поддържаща версия, когато има отворени бъгове за последната такава версия от серията? Това което производителите трябва да разберат е, че като отговорник за пакети за мен е по-сложно да поддържам кръпки за проблеми, които не се оправят в продукта.

Направих серия от бъгове за неподдържани възможности в MySQL Workbench (напр. 90620, 92900, 92908 и 94012), което ме накара да се замисля за значението на уеднаквяването на номерата на версиите, което беше направено с пускането на MySQL 8.0.11 като общодостъпна версия (виж MySQL 8.0: It Goes to 11!). Както разбирам публикацията, това беше направено, за да ползваме "правилната версия" на всички продукти. Това като цяло е добра идея, но не трябва ли "правилната версия" да поддържа същия набор от възможности като сървъра? Предполага се, че потребителите ще бъдат по-малко объркани ако е така, но за сега определено това не е случая с Workbench. Надявам се ситуацията да се подобри в бъдеще.

Искам да завърша тази статия с няколко завършващи мисли. Като разработчик съм наясно, че никога не може да има софтуер без никакви бъгове. Обаче, като потребител очаквам, когато бъг е правилно докладван и проблема е ясен решението да не трябва да отнема толкова много време, но разбира се това което наистина има значение са приоритетите, а те обикновено са толкова по-високи колкото броя на засегнатите потребители е по-голям.

2019-01-23

Нови възможности за разработчци в MySQL 8.0.14

С пускането на MySQL 8.0.14 Oracle запазва вече установената практика да въвежда нови възможности за разработчици дори с версии за поддръжка, които обикновено съдържат само малки подобрения и най-вече поправки на бъгове. Разгледах бележките към версията на 8.0.14, публикацията The MySQL 8.0.14 Maintenance Release is Generally Available на и разбира се наръчника, експериментирах и тук отдолу е моя избор на нови възможности свързани с разработка.

Латерални производни таблици (Lateral derived tables)

Преди MySQL 8.0.14 не беше възможно за производни таблици да се обръщат към (зависят от) колони на предходните таблици в FROM клаузата. Сега това ограничение е премахнато с добавянето на ключова дума LATERAL (виж Lateral derived tables). Ключовата дума LATERAL означава, че производната таблица зависи от предходната таблица от ляво. Можете да имате повече от една LATERAL производна таблица в заявка и всяка ще зависи само от предходната таблица или производна таблица. Латералните производни таблици са така наречения "for each" цикъл на SQL и това прави възможни някои операции, които иначе не са възможни или са по-малко ефикасни.
Ето един пример. Да кажем, че искате да изчислите минималната, средната и максималната заплата за всеки отдел в организацията. Преди трябваше да го напишете така:
План за изпълнение на заявката с производна таблица

SELECT D.dname, DT.min_sal, DT.avg_sal, DT.max_sal
  FROM dept D

       LEFT JOIN
       (SELECT E.deptno, MIN(E.sal) min_sal, AVG(E.sal) avg_sal, MAX(E.sal) max_sal
          FROM emp E
         GROUP BY E.deptno
       ) AS DT
       ON DT.deptno = D.deptno;


И така да използвате производна таблица DT, за да изчислите мин/сред/макс заплата за всички отдели от таблицата emp и тогава съедините с таблица dept получавайки следния резултат:

+------------+---------+-------------+---------+
| dname      | min_sal | avg_sal     | max_sal |
+------------+---------+-------------+---------+
| ACCOUNTING | 1300.00 | 2916.666667 | 5000.00 |
| RESEARCH   |  800.00 | 2175.000000 | 3000.00 |
| SALES      |  950.00 | 1566.666667 | 2850.00 |

| OPERATIONS |         |             |         |
+------------+---------+-------------+---------+
4 rows in set (0.0014 sec)


Производната таблица е напълно независима от другата съединена таблица, тъй като може да произведе резултат сама (т.е. не зависи от стойностите на колоните на другата таблица). Плана за изпълнение на тази заявка е даден от дясно и той потвърждава, че резултата на производната таблица първо бива материализиран, за да може да бъде съединен с другата таблица.
Друг подход би бил с използване на подзаявки в SELECT клаузата ето така:

SELECT D.dname,
       (SELECT MIN(E.sal) FROM emp E WHERE E.deptno = D.deptno) AS min_sal,
       (SELECT AVG(E.sal) FROM emp E WHERE E.deptno = D.deptno) AS avg_sal,
       (SELECT MAX(E.sal) FROM emp E WHERE E.deptno = D.deptno) AS max_sal
  FROM dept D;


което няма да бъде ефикасно (представете си таблица с продажби и хиляди търговци ако искате да оцените техните продажби), защото три заявки ще трябва да вършат работата на една. Не е възможно да се ползва само една подзаявка за изчисляване на всички необходими стойности в SELECT, защото такива подзаявки трябва да са скаларни. Подобна заявка ще предизвика грешка Error Code: 1241. Operand should contain 1 column(s) ако пробвате.
Ако се опитате да свържете производната таблица към другата таблица с заявка като следната:

SELECT D.dname, DT.min_sal, DT.avg_sal, DT.max_sal
  FROM dept D,
       (SELECT MIN(E.sal) min_sal, AVG(E.sal) avg_sal, MAX(E.sal) max_sal
          FROM emp E
         WHERE E.deptno = D.deptno
       ) AS DT;


ще получите грешка Error Code: 1054. Unknown column 'D.deptno' in 'where clause', защото таблица D не е позната на производната таблица. Заявката е незаконна в SQL-92, но в SQL-1999 става законна ако производната таблица се предшества от ключовата дума LATERAL:

SELECT D.dname, LDT.min_sal, LDT.avg_sal, LDT.max_sal
  FROM dept D,
       LATERAL
       (SELECT MIN(E.sal) min_sal, AVG(E.sal) avg_sal, MAX(E.sal) max_sal
          FROM emp E
         WHERE E.deptno = D.deptno
       ) AS LDT;

План за изпълнение на заявката с латерална производна таблица

и създава следния резултат:

+------------+---------+-------------+---------+
| dname      | min_sal | avg_sal     | max_sal |
+------------+---------+-------------+---------+
| ACCOUNTING | 1300.00 | 2916.666667 | 5000.00 |
| RESEARCH   |  800.00 | 2175.000000 | 3000.00 |
| SALES      |  950.00 | 1566.666667 | 2850.00 |
| OPERATIONS |    NULL |        NULL |    NULL |
+------------+---------+-------------+---------+
4 rows in set (0.1182 sec)


Както се вижда от графиката на плана за изпълнение отдясно в този случай няма групиране, но MySQL дава по-висока цена, защото достъпа до производната таблица е чрез пълно сканиране на таблица. По-интересната информация обаче е в табличния план за изпълнение (колона partitions е преднамерено скрита):

+----+-------------------+------------++------+---------------+-----------+---------+-------------------+------+----------+----------------------------+
| id | select_type       | table      || type | possible_keys | key       | key_len | ref               | rows | filtered | Extra                      |
+----+-------------------+------------++------+---------------+-----------+---------+-------------------+------+----------+----------------------------+
|  1 | PRIMARY           | D          || ALL  | NULL          | NULL      | NULL    | NULL              |    4 |      100 | Rematerialize (<derived2>) |
|  1 | PRIMARY           | <derived2> || ALL  | NULL          | NULL      | NULL    | NULL              |    2 |      100 | NULL                       |
|  2 | DEPENDENT DERIVED | E          || ref  | fk_deptno     | fk_deptno | 5       | dept_emp.D.deptno |    4 |      100 | NULL                       |
+----+-------------------+------------++------+---------------+-----------+---------+-------------------+------+----------+----------------------------+
3 rows in set, 2 warnings (0.0010 sec)
Note (code 1276): Field or reference 'dept_emp.D.deptno' of SELECT #2 was resolved in SELECT #1
Note (code 1003): /* select#1 */ select `dept_emp`.`d`.`dname` AS `dname`,`ldt`.`min_sal` AS `min_sal`,`ldt`.`avg_sal` AS `avg_sal`,`ldt`.`max_sal` AS `max_sal` from `dept_emp`.`dept` `d` join lateral (/* select#2 */ select min(`dept_emp`.`e`.`sal`) AS `min_sal`,avg(`dept_emp`.`e`.`sal`) AS `avg_sal`,max(`dept_emp`.`e`.`sal`) AS `max_sal` from `dept_emp`.`emp` `e` where (`dept_emp`.`e`.`deptno` = `dept_emp`.`d`.`deptno`)) `ldt`

Има две нови информация и допълнителна бележка. Плана ясно показва, че производната таблица E (derived2) е зависима (DEPENDENT) от другата таблица и че тя се материализира отново за всеки ред от D (виж EXPLAIN extra information). Това е причината поради която латералните производни таблици са познати също като "for each" цикъла на SQL. Бележката дава информация за това как външното позоваване в производната таблица е разрешено.

Разбира се MySQL Workbench (дори надграден до 8.0.14 също) отново не е запознат с новия синтаксис (виж предишната ми публикация Нови възможности за разработчици в MySQL 8.0.13), защото не оцветява правилно новата ключова дума и показва грешка в SQL редактора точно след нея. Докладвах това като бъг 94012, но нямам много надежда, тъй като 90620, 92900 и 92908 бяха потвърдени, но са все още отворени. Номерата на версиите нямат голямо значение в днешни дни :-)

Моля, обърнете внимание, че е възможно да се направи връзка с външната таблица ако производната таблица е в подзаявка (виж пример в WL#461).

Агрегатни JSON функции вече може да се ползват като прозоречни функции

Вече е възможно да се ползват агрегатни функции JSON_ARRAYAGG и JSON_OBJECTAGG като прозоречни функции с използването на OVER клауза (виж Window Function Concepts and Syntax). Това прави всички (освен COUNT(DISTINCT) и GROUP_CONCAT) от агрегатните функции възможни за употреба като прозоречни функции след като побитовите AND/OR/XOR функции бяха направени такива с MySQL 8.0.12. Ето един пример:

SELECT E.ename, E.sal,
       AVG(E.sal) OVER dw AS avg_sal,
       JSON_OBJECTAGG(D.dname, E.sal) OVER dw AS dept_sal
  FROM emp  E,
       dept D
 WHERE E.deptno = D.deptno
WINDOW dw AS (PARTITION BY D.deptno);


+--------+---------+-------------+------------------------+
| ename  | sal     | avg_sal     | dept_sal               |
+--------+---------+-------------+------------------------+
| CLARK  | 2450.00 | 2916.666667 | {"ACCOUNTING": 1300.0} |
| KING   | 5000.00 | 2916.666667 | {"ACCOUNTING": 1300.0} |
| MILLER | 1300.00 | 2916.666667 | {"ACCOUNTING": 1300.0} |
| SMITH  |  800.00 | 2175.000000 | {"RESEARCH": 3000.0}   |
| JONES  | 2975.00 | 2175.000000 | {"RESEARCH": 3000.0}   |
| SCOTT  | 3000.00 | 2175.000000 | {"RESEARCH": 3000.0}   |
| ADAMS  | 1100.00 | 2175.000000 | {"RESEARCH": 3000.0}   |
| FORD   | 3000.00 | 2175.000000 | {"RESEARCH": 3000.0}   |
| ALLEN  | 1600.00 | 1566.666667 | {"SALES": 950.0}       |
| WARD   | 1250.00 | 1566.666667 | {"SALES": 950.0}       |
| MARTIN | 1250.00 | 1566.666667 | {"SALES": 950.0}       |
| BLAKE  | 2850.00 | 1566.666667 | {"SALES": 950.0}       |
| TURNER | 1500.00 | 1566.666667 | {"SALES": 950.0}       |
| JAMES  |  950.00 | 1566.666667 | {"SALES": 950.0}       |
+--------+---------+-------------+------------------------+
14 rows in set (0.0021 sec)


Важно е да се отбележи, че MySQL не разрешава повторение на ключове в JSON типа данни, така че в прозорец без подредба функцията JSON_OBJECTAGG ще върне последната стойност за ключа, което може да е неопределено.

Подобрения по X протокол

Според бележките към версията данните вече винаги се обръщат в символния набор utf8mb4 (с използване на utf8mb4_general_ci колация). Другото забележително подобрение е поддръжката на функционалност за подготвяне на заявки. Бележките към версията не предоставят препратка към тази нова функционалност, но е споменал WL#9270 в своята статия, така че вярвам става въпрос за подготвяне на CRUD операции (виж Preparing CRUD Statements). Един прост пример на JavaScript ще бъде следното:

MySQL Shell 8.0.14
Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
Other names may be trademarks of their respective owners.
 MySQL  JS > \connect user@localhost
Creating a session to 'user@localhost'
Your MySQL connection id is 22 (X protocol)
Server version: 8.0.14 MySQL Community Server - GPL
 MySQL  localhost:33060+ ssl  JS > \use test
Default schema `test` accessible through db.
 MySQL  localhost:33060+ ssl  test  JS > var usr = db.createCollection('users')
 MySQL  localhost:33060+ ssl  test  JS > usr.add({name:"User1",age:15})
Query OK, 1 item affected (0.0108 sec)
 MySQL  localhost:33060+ ssl  test  JS > usr.add({name:"User2",age:17})
Query OK, 1 item affected (0.0138 sec)
 MySQL  localhost:33060+ ssl  test  JS > usr.add({name:"User3",age:20})
Query OK, 1 item affected (0.0105 sec)
 MySQL  localhost:33060+ ssl  test  JS > usr.add({name:"User4",age:19})
Query OK, 1 item affected (0.0137 sec)
 MySQL  localhost:33060+ ssl  test  JS > usr.add({name:"User5",age:16})
Query OK, 1 item affected (0.0118 sec)
 MySQL  localhost:33060+ ssl  test  JS > usr.find()
[
    {"_id": "00005c46e7e5000000000000000a","age": 15,"name": "User1"},
    {"_id": "00005c46e7e5000000000000000e","age": 17,"name": "User2"},
    {"_id": "00005c46e7e5000000000000000f","age": 20,"name": "User3"},
    {"_id": "00005c46e7e50000000000000010","age": 19,"name": "User4"},
    {"_id": "00005c46e7e50000000000000011","age": 16,"name": "User5"}
]
5 documents in set (0.0006 sec)
 MySQL  localhost:33060+ ssl  test  JS > var fcmd = usr.find('age >= :page')
 MySQL  localhost:33060+ ssl  test  JS > fcmd.bind('page', 18)
[
    {"_id": "00005c46e7e5000000000000000f","age": 20,"name": "User3"},
    {"_id": "00005c46e7e50000000000000010","age": 19,"name": "User4"}
]
2 documents in set (0.0006 sec)
 MySQL  localhost:33060+ ssl  test  JS > fcmd.bind('page', 16)
[
    {"_id": "00005c46e7e5000000000000000e","age": 17,"name": "User2"},
    {"_id": "00005c46e7e5000000000000000f","age": 20,"name": "User3"},
    {"_id": "00005c46e7e50000000000000010","age": 19,"name": "User4"},
    {"_id": "00005c46e7e50000000000000011","age": 16,"name": "User5"}
]
4 documents in set (0.0003 sec)

 MySQL  localhost:33060+ ssl  test  JS > fcmd.bind('page', 17)
[
    {"_id": "00005c46e7e5000000000000000e","age": 17,"name": "User2"},
    {"_id": "00005c46e7e5000000000000000f","age": 20,"name": "User3"},
    {"_id": "00005c46e7e50000000000000010","age": 19,"name": "User4"}
]
3 documents in set (0.0004 sec)


римера създава колекция от потребители с техните имена и възраст, после печата цялата колекция. Интересната част започва с реда подчертан в жълто. Той подготвя израз използвайки именуван параметър (анонимни параметри с ? не се поддържат от X протокола), но не го изпълнява. Изпълнението се случва след като се свърже стойност към параметъра и това може да се прави много пъти произвеждайки различни резултати. Интересно е, че в общия журнал първото свързване всъщност изпълнява заявка, след това има подготовка и след това има изпълнения:

Query    SELECT doc FROM `test`.`users` -> usr.find()
Query    SELECT doc FROM `test`.`users` WHERE (JSON_EXTRACT(doc,'$.age') >= 18) -> fcmd.bind('page', 18)
Prepare    SELECT doc FROM `test`.`users` WHERE (JSON_EXTRACT(doc,'$.age') >= ?)
Execute    SELECT doc FROM `test`.`users` WHERE (JSON_EXTRACT(doc,'$.age') >= 16) -> fcmd.bind('page', 16)
Execute    SELECT doc FROM `test`.`users` WHERE (JSON_EXTRACT(doc,'$.age') >= 17) -> fcmd.bind('page', 17)


Използването на подготвени изразни за многократно изпълнявани изрази може да доведе до подобрения в производителността заради спестеното време за синтактичен разбор на заявката, затова това е нещо което трябва да имате в предвид ако трябва да подобрите производителността на приложенията и скриптовете си.

Подобрения в работа с пространствени данни

Функцията ST_Distance вече приема като незадължителен трети параметър мерната единица за връщаната стойност. Възможните стойности са определени в таблица INFORMATION_SCHEMA.ST_UNITS_OF_MEASURE заедно с коефициент за превръщане към основната единица метър (metre), което е и стойността по подразбиране. Ето един пример за изчисляване на разстоянието между София и Сидни в километри и морски мили в SRID 4326:

SELECT ST_Distance(ST_PointFromText('POINT( 42.69751 23.32415)', 4326),
                   ST_PointFromText('POINT(-33.86667 151.20000)', 4326)) / 1000 dist_km;
+--------------------+
| dist_km            |
+--------------------+
| 15431.933058990671 |
+--------------------+
1 row in set (0.0023 sec)


SELECT ST_Distance(ST_PointFromText('POINT( 42.69751 23.32415)', 4326),
                   ST_PointFromText('POINT(-33.86667 151.20000)', 4326), 'nautical mile') dist_nm;


+------------------+
| dist_nm          |
+------------------+
| 8332.57724567531 |
+------------------+
1 row in set (0.0008 sec)

Това завършва моя преглед. Има разбира се много повече в MySQL 8.0.14 не само за разработчици, така че ви насърчавам да изследвате и откриете повече. Препратките в началото на тази статия са добри като начало.

2019-01-15

Трябва ли Slackware да въведе systemd?

Две скорошни събития ме накараха да напиша тази публикация. Първото беше потребител в групата LinuxSlackware във Facebook, който благодареше на Patrick, че не е преминал към systemd. Второто беше скорошен случай където нощната работа на едно приложение пускаше твърде много тежки процеси на един MySQL 8 сървър под RHEL 7, което доведе до изчерпване на паметта и неотговаряща услуга. Във втория случай systemd действа да убие mysqld процеса и да го пусне отново, така че услугата беше възстановена и приложението поне можеше да продължи да работи.

Сега, първото събитие е просто израз на мнение на потребител, което аз, като дългогодишен потребител на Slackware, мога да разбера, защото става въпрос за нещо, с което сме свикнали. То е просто и работи поне за повечето неща.

Обаче, второто събитие ме накара да се замисля, че под Slackware това нямаше да се случи, освен ако не си го направите сами някак си. Въпросът е дали си заслужава, когато вече е направено и работи добре на всички други Линукси, които използвам?

2019-01-10

Нещата които научих за себе си през 2018

За мен 2018-та беше година на надежди, някои от които не се осъществиха. Надявах се на някои промени, но в крайна сметка те не се случиха, в което разбира се няма нищо трагично, тъй като успях да науча някои неща за себе си. В тази статия поглеждам назад към това, което научих за себе си през изминалата година.

Нямам достатъчно опит с отворен код

Това ми беше казано някъде в началото на Март и въобще не възразявам. Допринесъл съм малки кръпки, преводи и буболечки към различни проекти с отворен код в изминалите около 20 години, публикувал съм някои от личните си проекти под отворени лицензи, но ми се иска да бях направил повече. Вярно е, че можех да допринеса повече, но за съжаление просто не намерих времето за това. Все още силно вярвам в софтуера със свободен и отворен код (FOSS), който използвам от университетските ми години, все още използвам и ще продължавам да използвам и за напред. Обаче, в днешни дни не съм толкова придирчив за това, че FOSS трябва също така да е безплатен като в "безплатна бира", което ме приближава към философията на GNU.

Не чета достатъчно/вашите книги

Беше ми предложено от някои да чета повече книги. Да, вероятно мога да чета повече, но все пак чета. И може да не чета същото като вас и вие не сте тези, които ще ми кажете какво да чета както и аз не казвам на вас. Освен книги чета също и някои списания както съм го правил от тийнейджърските си години. И някои неща в мрежата. С тази публикация искам да благодаря на семейството си и приятелите, които ми подариха книги миналата година, така че сега имам към 10 книги в списъка си за 2019. Съжалявам, списъка е лична информация ;-)

Нямам достатъчно опит с тази или онази технология

Научих това през лятото. Все още се смятам за любопитен човек относно технологията, но просто няма как да имам опит с всичко там и не вярвам, че някой може. За мен е по-интересно как човек възприема технологиите и как се справя с промените. Преди две десетилетия софтуера беше доста различен както и инструментите и технологиите, с които се правеше. Темпото на промените се увеличи както и хората заети в разработката на софтуер. Има много хора започнали да разработват софтуер преди едно, две, три десетилетия и повече, които лесно се приспособяват към нови инструменти и технологии, защото имат основни разбирания за това как се прави и работи софтуера. И което е по-важно те знаят, че трябва да учат постоянно и да се включат. Това означава да четат наръчниците, да следят и се възползват от промените, да обсъждат, да докладват буболечки, да правят предложения за нови функционалности, да допринасят колкото им е възможно и като цяло да се включат в общността.

Не съм гъвкав

Това е нещо което ми беше казано преди Коледните празници. Да, вероятно, не съм толкова гъвкав все пак. Запознат съм и разбирам ценностите и принципите на гъвкавата методология, но в същото време не ги споделям напълно.
  • Съгласен съм, че хората и взаимодействията се по-важни от процесите и инструментите, но само второто може да направи първото лесно и ефективно. Без ясни процеси и правилните инструменти дори най-добрите ще се провалят да взаимодействат по начин, който облагодетелства разработката на софтуер. Вярвам, че такива хора първо ще създадат необходимите процеси и инструменти.
  • Съгласен съм, че работещия софтуер е по-важен от подробната документация, но съм виждал софтуер написан с оскъдна или въобще никаква документация. След известно време (напр. 5, 10, 15 години) когато такъв софтуер спре да работи, трябва да бъде надграден или да бъде обяснен на клиент, той първо трябва да мине през процес на обратно инженерство, тъй като най-вероятно първоначалните разработчици са напуснали отдавна компанията. Затова в краткосрочен план и за малки проекти напълно поддържам това, но в дългосрочен план и за големи проекти не мисля, че помага на някого. Проблемът е, че ако не се напише навреме документацията най-вероятно никога няма да бъде написана.
  • Съгласен съм, че сътрудничеството с клиента е по-важно от прегорите по договора, но не всички клиенти могат да сътрудничат ефективно. Някои клиенти дори не знаят какво точно искат, не четат документи (и дори отказват да го правят когато са помолени) съответно не познават софтуера, който ползват и не прехвърлят правилно знанието през времето. Това са само моите скромни наблюдения, но вярвам, че сътрудничеството с клиента трябва да бъде отворено в смисъл, че трябва да бъде достъпно до всички заинтересовани страни сега и в бъдеще. Виждал съм случаи, в които софтуера е разработен в "тясно сътрудничество" между разработчик и човек от страна на клиента чрез лични съобщения, за който след това никой не знае нищо и не иска да знае.
  • Съгласен съм, че реакцията на промените е по-важно от следването на план, но реакцията на постоянни промени означава, че няма план и обхват на проекта въобще. Проектите, които започват като мехурче и постоянно се променят лесно се деформират и излизат извън контрол. Такива проекти обикновено се провалят. Виждал съм някои никога не свършващи проекти в практиката си и това е едно от нещата, които наистина не харесвам.
Тези и вероятно други причини карат организациите да избират хибридни подходи в разработката на софтуер, защото намират гъвкавата методология за твърде крайна и неефективна в големи организации, а аз съм работил в такива в последните повече от 15 години, така че вероятно възгледите ми са изкривени от тази перспектива.

Аз съм 'напреднал потребител'

Това ми беше писано от представител на поддръжката. Случи се след като поисках (и настоях) за някои прости (според мен) подобрения в софтуера предоставян и поддържан от голям доставчик, за който той работеше. То го написа в смисъл, че само аз искам тези подобрения, от което автоматично ми стана ясно, че те няма да бъдат направени. Е, ако това ме определя като "напреднал потребител", така да бъде, но съм доста разочарован, защото очаквах повече от SOHO устройство използващо софтуер със свободен и отворен код. Може да напиша отделна статия за това по-късно.

Аз съм себе си

Накрая, но не на последно място аз все още съм себе си с всичките си предимства и недостатъци като човек и професионалист. Нямам претенции да съм всичко за всеки, нямам претенции, че знам всичко и нямам претенции да съм най-опитния там. Всичко, което научих за себе си през 2018-та, беше основано на някакъв опит и съм сигурен, че ще ми помогне да стана по-добър човек и професионалист в бъдеще.

2018-12-29

Разпознаване на функционални зависимости при групиране от MySQL

Ако сте използвали MySQL в миналото трябва да помните, че беше възможно по подразбиране да се сумира в заявки без GROUP BY клауза. Така можеше да напишете заявка като тази

SELECT D.dname, COUNT(E.empno) cnt
  FROM dept D,
       emp  E
 WHERE D.deptno = E.deptno;

най-вероятно с идеята да преброите служителите във всеки отдел. Обаче, резултата, който ще получите (без грешка) ще бъде:

+------------+-----+
| dname      | cnt |
+------------+-----+
| ACCOUNTING |  14 |
+------------+-----+

което просто е грешно и най-вероятно не това което искате в действителност. Такава заявка ще предизвика грешка в други бази данни (напр. Oracle ще даде ORA-00937: not a single-group group function, PostgreSQL ще каже SQL state: 42803 ERROR: column "d.dname" must appear in the GROUP BY clause or be used in an aggregate function, и т.н.).

Причината за това поведение беше SQL режима по подразбиране на сървъра и по-точно отсъствието на ONLY_FULL_GROUP_BY в списъка с режими. Има някои приложения (като Bugzilla и Cacti) които не ми позволяваха да разреша този режим на сървъра ми въпреки, че той беше отдавна препоръчван от някои (напр. спомням си представянията на Ronald Bradford от Есенната конференция на БГПО). Помня, че имах проблеми с тези приложения, така че сложих коментар в конфигурационния файл да не разрешавам ONLY_FULL_GROUP_BY. Трябва да призная, че не съм проверявал скоро, така че ситуацията може да се е подобрила, но с текущото състояние на Bugzilla е съмнително.

SQL режимите по подразбиране се промениха доста с последните версии на MySQL:
Единствената разлика в 8.0 в всъщност отсъствието на NO_AUTO_CREATE_USER, защото този режим беше премахнат след като бе обявен за остарял в предишните версии. Силно препоръчително е да придържате към тези режими по подразбиране особено при започване на ново приложение, за да не се налага да поправяте заявки и данни след това.

Когато ONLY_FULL_GROUP_BY беше разрешено с MySQL 5.7.5 (пуснат на 2014-09-25) Oracle се погрижи да изработи усъвършенствани функционални зависимости както Guilhem Bichot писа през Декември 2014. Усилието беше оценено от Markus Winand в неговата страхотна statiq One Giant Leap For SQL: MySQL 8.0 Released от Април 2018, който написа, че MySQL вече има "най-пълната проверка за функционални зависимости сред основните SQL бази данни". Да изследваме това, но преди това можете да се обърнете към Wikipedia за бърз преглед на определението за функционална зависимост. Казано просто Y е функционално зависим от X ако стойностите на X уникално определят стойностите на Y. Например стойностите на колоните в таблица са функционално зависими от първичния ключ, защото стойността му определя уникално всички други стойности в реда.
Вижте следния пример с първичен ключ. Това е заявката от горе, но с GROUP BY клауза:

SELECT D.dname, COUNT(E.empno) cnt
  FROM dept D,
       emp  E
 WHERE D.deptno = E.deptno
 GROUP BY D.deptno;

Правилна ли е тази заявка? Групирането е по D.deptno, но списъка с колони в SELECT съдържа само D.dname и сумиране по E.empno, така че е грешна, нали? Не и в MySQL 5.7 и по-нов с разрешен ONLY_FULL_GROUP_BY режим, така че старото правило да се слага всичко от GROUP BY клаузата в SELECT или да се ползва сумиране (т.е. без повече SQL-92 ограничения). Резултата е:

+------------+-----+
| dname      | cnt |
+------------+-----+
| ACCOUNTING |   3 |
| RESEARCH   |   5 |
| SALES      |   6 |
+------------+-----+

Защо така? MySQL правилно определя, че D.dname е функционално зависим от D.deptno, защото това е първичния ключ за dept таблицата. Същата заявка работи правилно и в PostgreSQL, но не и в Oracle и други основни SQL бази данни. MySQL може правилно да определя функционални зависимости също по уникални ключове, равенства в WHERE клауза или [LEFT] JOIN ON клаузи и от избрани изрази в тялото на изгледи и производни (derrived) таблици. Доста вълнуващо, нали? Гледайте да го използвате следващия път когато решите да пишете заявка с GROUP BY клауза.

За повече подробности, моля обърнете се към глава Detection of Functional Dependence в наръчника.

    2018-11-28

    Смяна на плана за изпълнение в MySQL 8.0

    Преди около 6 седмици в работата трябваше да напиша нова заявка представляваща доклад, защото страницата на едно приложение се зареждаше бавно (около 25 секунди). Оказа се, че име проблем с приложението, защото то първо изпълняваше сравнително бавна заявка (отнемаща около 6 секунди) и тогава сравнително бърза заявка за всеки ред върнат от първата заявка. Втората заявка отнемаше между 0.3 и 0.5 секунди за изпълнение, но се изпълваше около 60 пъти, така че закъснението се натрупваше и обясни забавянето на страницата.
    Реших да предоставя една единствена заявка създаваща целия резултат, така че трябваше да съединя 6 таблица. Една таблица (msg_progs) имаше само 13 реда, други две имаха само 64 реда (bldenv и prjprocstat), а друга само около 100 реда (p) тези четирите нямаха тенденция да растат много повече. Имаше една таблица с няколко десетки хиляди реда (m) и друга с няколко стотин хиляди реда (mtp), които със сигурност щяха да растат всеки ден. За да подобря избирателността във втората по големина таблица (m) създадох съставен индекс по три колони използвани от заявката, но проблема беше, че все още се избираха повечето от данните в таблицата. Така приключих с заявка изпълняваща се за 6-7 секунди, което беше доста по-приемливо от първоначалните 25 секунди. Не мога да разкрия всички подробности, но направих снимка на плана за изпълнение и смятам, че няма проблем да го публикувам.
    Query explain plan in MySQL 5.7.20
    Забележка: MySQL Workbench показва страхотно планове за изпълнение графично (забележете линиите свърващи различните операции - колкото по-голям е броя на редовете толкова по-дебела е линията). Текстовите планове за изпълнение са вече минало, нали?

    Това беше до преди 3 седмици когато надградихме до MySQL 8.0.13 (от 5.7.20). Същата заявка и в същата база започна да се изпълнява по 24 секунди. Когато проверих плана за изпълнение той се беше променил (виж отдолу).
    Original explain plan in MySQL 8.0
    Мисля, че можете лесно да забележите, че реда на съединяване се е променил. Преди втората по големина таблица се взимаше в предвид в първия вложен цикъл, след това най-голямата таблица и накрая по-малките таблици. Сега е обратното, защото оптимизатора първо взема в предвид по-малките таблици и само тогава по-големите. Можех да използвам подсказка, за да оправя реда на съединяване (напр. JOIN_ORDER), но забелязах, че ако пресъздам съставния индекс по само две от колоните (които всъщност дават по-добра избирателност) оптимизатора избира различен план, но заявката все още се изпълвана за около 14 секунди. Накрая реших да премахна една от таблиците в съединението (най-малката msg_progs само с 13 реда), което накара оптимизатора да избере друг план за изпълнение (тук отдолу) и заявката вече се изпълнява за около 4 секунди.
    Finaly explain plan in MySQL 8.0
    Не съм сигурен от къде идва тази разлика освен от сървъра, но не мога да обвинявам без доказателство (напр. препратка към наръчника, която обяснява разликата), което за съжаление нямах време да търся. Ако знаете причината за смяната на плана за изпълнение в MySQL 8.0 моля уведомете ме.
    Ситуацията ми напомни за базата на Oracle където след всяка главна версия имахме заявки с променен план за изпълнение и трябваше да използваме подсказки или други номера, за да ги забързаме отново. Изглежда да няма разлика при MySQL днес.
    Обновяване 2018-12-17: Направих прибързано заключение по-горе, защото промяната на плана се случи (или поне беше забелязана) след надграждането на сървъра. Обаче, както се оказа истинската причина беше броя на редовете в таблиците m и mtp заради който сървъра избираше различен план на изпълнение. Открихме това експериментирайки с различни по обем набори с данни.

    2018-11-27

    Игра на авторитети

    В управленската теория и практика има случаи в които е необходимо да се използва авторитет (напр. за налагане на крайни срокове, за решаване на конфликти, и т.н.). Само че, докато разбирам нуждата, наистина не разбирам управители на които се налага да използват авторитета си когато са останали без всякакъв друг аргумент. Имал съм ситуации в миналото когато авторитет е бил необходим, но винаги съм подхождал с неохота и никога не съм ползвал без сериозен аргумент (което е било любезно признавано). Това което имам в предвид, е че не мога просто да наложа нещо на някого ако нямам добра причини за това и ако не съм го оправдал пред засегнатите страни.
    Въпросът е, че понякога аргументите на страните в обсъждане могат да изглеждат еднакво важни и действителни, така че нуждата от арбитраж и използването на авторитет може да се неизбежни. Един начин да се реши това е да се основат следващите аргументи на предишни факти като преди това записани обсъждания, споразумения, процедури и практики относно същата или сродна материя. Ами ако другата страна просто не приеме допълнителните аргументи? Е, наистина не знам, но мисля, че това говори достатъчно за другата страна. Във фирмените общества без добро управление на документацията и постоянна смяна на авторитетите знанието бързо се губи във времето. Нови правила и процедури се създават всеки ден, защото никой не я наясно или не желае да приеме това което е било определено в миналото. Така се създават легенди.

    2018-11-15

    Маскиране на данни и анонимизация в изданието за обществото на MySQL

    Oracle наскоро въведе функционалност за маскиране на данни в MySQL, но тя е достъпна само в изданието за предприятия (виж MySQL Enterprise Data Masking and De-Identification в наръчника). Не съм клиент, но съм запознат с идеята както и със съхранени процедури, така че реших да се опитам да емулирам това което MySQL са изработили за тяхното издание за предприятия на базата.

    Започнах с четене на наръчника, после прегледах форматите за номера на кредитна карта и социална осигурвока, за да изляза накрая с просто изпълнение изработено тази вечер. Изработих само функциите за маскиране с общо и специално предназначение и функциите за създаване на случайни данни (напр. е-поща, номер на карта за разплащания, номер на социална осигуровка и телефонен номер в САЩ). Имам допълнителна функция gen_rnd_string за създаване на низ от случайни символи с дадена дължина (само знаци от английската азбука по подразбиране). Направих няколко теста и резултатите изглеждат обещаващо, но трябва да пробвам повече и вероятно да създавам по-добри данни, които евентуално да минават проверка, защото приложение използващо случайно създадени данни може лесно да се счупи ако не може да ги провери. Остава ми да изработя функциите за създаване на случайни данни с използване на речници за които смятам, че мога да ползвам LOAD DATA INFILE и CREATE TEMPORARY TABLE за зараждане на речник.

    Това е просто моята идея за това как може да бъде изработено маскиране на данни и анонимизация дори в изданието за обществото на MySQL и като съхранени процедури, защото реализацията на MySQL се основава на потребителски дефинирани функции (UDF). Чудя се защо са избрали този подход и отдолу опита ми да отгатна възможните отговори:
    • Видимост: Потребителски дефинираната функция има глобална видимост, така че може да бъде извикана без името на схемата отпред като всяка друга вградена функция. Моята реализация трябва да бъде заредена в схема и така всяка функция ще трябва да се вика с име на схемата отпред.
    • Подразбрани аргументи: MySQL все още не поддържа подразбрани аргументи за съхранени процедури, но това е възможно за потребителски дефинирани функции тъй като те могат да се написани само "на C или C++ (или друг език, който може да използва C конвенция на извикванията)". Функциите за маскиране с общо предназначение mask_inner и mask_outer приемат незадължителен аргумент за символа с който да се маскира. В реализация със съхранени процедури това може да стане само с потребителски дефинирана променлива. Ще изработя това по-късно.
    • Скриване на подробностите по изработката: Потребителски дефинираните функции скриват изработката на MySQL в двоичния файл на споделената библиотека. Изходния код на съхранените процедури може лесно да бъде разгледан от потребител с достатъчно права (напр. администратор). До колкото ми е известно MySQL не предлага инструмент за разбъркване на кода както Oracle (виж Obfuscating PL/SQL Source Code). Отделно от това наръчника не предоставя достатъчно подробности за възможните стойности от функциите за създаване на данни.
    • Сигурност: Това може да се отнася до начина по който сървъра управлява паметта за потребителски дефинираните функции и съхранените процедури, но това е твърде навътре в нещата.
    • Производителност: Потребителски дефинираните функции по предположение трябва да имат по-добра производителност в сравнение със съхранените процедури, защото първите са извиквания на функции в компилирана за съответната система съхранена библиотека, а вторите се интерпретират от сървъра при всяко извикване.
    Нетърпелив съм да обсъдя това утре на Есенната конференция на БГПО 2018 където ще има представяне на точно същата тема. Ще обновя публикацията или ще напиша нова след събитието.

    Обновяване 2018-11-16 19:01 EET: BGOUG конференцията потвърди представите ми за паричните, поради които маскирането на данни е изработено като UDF функции. Една от цитираните причини беше точно "производителност".

    Обновяване 2018-11-18 19:51 EET: След като си поиграх още малко и опитах да направя речниковите функции днес, се сблъсках с две ограничения, за които не бях помислил преди това. Първото ограничение е невъзможността за използване на подготвени изрази във функция (т.е. от главата Prepared SQL Statement Syntax в наръчника "SQL syntax for prepared statements can be used within stored procedures, but not in stored functions or triggers"). Второто ограничение е невъзможността за използване на LOAD DATA INFILE като подготвен израз (виж секция SQL Syntax Allowed in Prepared Statements в наръчника). Първото ограничение означава, че речниковите функции могат да бъдат изработени само като процедури. Второто ограничение означава, че е невъзможно да се зареди речника от файл, така че изработката на функция gen_dictionary_load не може да бъде завършено - тя може да създаде временната таблица за речника, но не може да го зареди. Все пак направих процедурите и успях да създам случайни градове в САЩ. Също така проверих производителността с gen_rnd_pan функцията, тъй като тя прави както създаване на случаен низ от цифри, така и изчисление на числото на Лун. На моя MySQL 5.7 сървър работещ върху Intel Pentium G3420 @ 3.2 GHz с 8 GB DDR3 RAM пуснах SELECT BENCHMARK(count, gen_rnd_pan()) и резултата за различен брой повторения беше следния:
    • 1000 пъти - 0.28 секунди;
    • 10000 пъти - 3.13 секунди;
    • 100000 пъти - 31.41 секунди;
    • 1000000 пъти - 278.19 секунди.
    Освен ако MySQL не премахне ограниченията за мен темата е приключена. Мога да се опитам да направя функциите за маскиране на данни като UDF както MySQL ако намеря време и ако имам истинска нужда.

    2018-10-23

    Нови възможности за разработчици в MySQL 8.0.13

    Ах, каква седмица вече и още не е приключила! Първо, Oracle пуснаха Oracle XE 18c (както писах в Неделя), а вчера (22-ри Октомври) пристигна MySQL 8.0.13 заедно (както вече обичайно) с целия флот (Router, Shell, Workbench и C++/J/ODBC/NET/Python/Node.js Connectors). Бях нетърпелив да прочета журнала с промените и както посочената статия от блога на MySQL Server също подсказва има някои интересни нови възможности за дизайнери и разработчици. Тук отдолу е моя избор и няколко думи за надграждане от 8.0.12.

    Стойностите по подразбиране в SQL вече могат да са също функция или израз

    Вече е възможно да има стойности по подразбиране като функция или израз. Това е важна нова възможност. Преди само буквални стойности бяха позволени, което беше доста ограничаващо. Премахването на това ограничение означава, че вече е възможно:
    • да се създават UUID стойности по подразбиране (с използването на DEFAULT (uuid_to_bin(uuid())) например);
    • да се създават стойности по подразбиране за геометрични типове данни (с използването на DEFAULT (POINT(0,0)) или DEFAULT (ST_PointFromText('POINT(42.69751 23.32415)', 4326)) например);
    • да се създават стойности по подразбиране за JSON колони (с използването на DEFAULT (JSON_ARRAY()) или DEFAULT ('[]') например); и
    • други сложни стойности (като DEFAULT (CURRENT_DATE + INTERVAL 1 DAY) or DEFAULT (PI() * POW(r, 2)) например) с използването на функция или функции в израз.
    Забележка: Моля, обърнете внимание на скобите обграждащи израза в DEFAULT (виж Handling of Explicit Defaults as of MySQL 8.0.13 в наръчника). MySQL Workbench 8.0.13 отново има някои проблеми с синтактичния разбор на новия синтаксис (виж екранната снима отдолу), така че отворих бъг 92900 и той вече беше потвърден:

    MySQL Workbench 8.0.13 грешка с изрази по подразбиране


    Пробвах новата функционалност със следното:

    CREATE TABLE def_expr (
      id        INT           NOT NULL AUTO_INCREMENT,
      uuid_def  BINARY(16)    DEFAULT (uuid_to_bin(uuid())),
      geo_def   POINT         DEFAULT (Point(0,0)),
      geo_def2  GEOMETRY      DEFAULT (ST_PointFromText('POINT(42.69751 23.32415)', 4326)),
      json_def  JSON          DEFAULT (JSON_ARRAY()),
      json_def2 JSON          DEFAULT ('[]') /* this works too */,
      tomorrow  DATE          DEFAULT (CURDATE() + INTERVAL 1 DAY),
      radius    INT           DEFAULT (FLOOR(1 + (RAND() * 10))),
      area      DECIMAL(10,3) DEFAULT (ROUND(PI() * POW(radius, 2), 3)),

      PRIMARY KEY (id),
      UNIQUE INDEX id_UNIQUE (id ASC) VISIBLE
    );



    След това вмъкнах две пробни линии и получих следните резултати:

    INSERT INTO def_expr VALUES (); /* x 2 */

    SELECT id,
           bin_to_uuid(uuid_def) uuid_def,
           ST_AsText(geo_def)    geo_def,
           ST_AsText(geo_def2)   geo_def2,
           json_def, json_def2,
           tomorrow, radius, area
      FROM def_expr;


    +----+--------------------------------------+------------+--------------------------+----------+-----------+------------+--------+---------+
    | id | uuid_def                             | geo_def    | geo_def2                 | json_def | json_def2 | tomorrow   | radius | area    |
    +----+--------------------------------------+------------+--------------------------+----------+-----------+------------+--------+---------+
    |  1 | a2747ee0-d6ee-11e8-b02e-02004c4f4f50 | POINT(0 0) | POINT(42.69751 23.32415) | []       | []        | 2018-10-24 |      2 |  12.566 |
    |  2 | a2ff920b-d6ee-11e8-b02e-02004c4f4f50 | POINT(0 0) | POINT(42.69751 23.32415) | []       | []        | 2018-10-24 |     10 | 314.159 |
    +----+--------------------------------------+------------+--------------------------+----------+-----------+------------+--------+---------+
    2 rows in set (0.0021 sec)


    Яко, нали? Нетърпелив съм да го пробвам с проектите ми.

    Функционални SQL индекси


    Вече можете да имате функционални индекси (виж Functional Key Parts в наръчника), което означава, че индекса се изгражда от стойности на изрази, вместо стойности на колони или началната част на стойности на колони. С други думи можете да индексирате стойности, които не са съхранени в таблица, което смятам, че може да бъде доста мощно в определени случаи. В съставни индекси с няколко части на ключа можете да смесвате функционални и нефункционални (обикновени) части. Функционалните части на ключа са реализирани, чрез скрити виртуални породени колони (виж CREATE TABLE and Generated Columns в наръчника), които са налични от MySQL 5.7.6 (от 2015-03-09). Това също така значи, че функционалните части на ключа имат същите ограничения (т.е. детерминирани вградени функции и оператори са разрешени, но подзаявки, параметри, променливи, съхранени и потребителски функци не са разрешени).
    За да пробвам новата функционалност се сетих, че IP адреси обичайно се съхраняват като текст в базите и после се четат и разбиват, но адреса може да се представи като целочислена стойност (без знак), което изисква само 4 байта. Така, че използвах следното:

    CREATE TABLE func_index (
      id        INT        NOT NULL AUTO_INCREMENT,
      ipaddr4   INT UNSIGNED /* 4 bytes */,

      PRIMARY KEY (id),
      UNIQUE INDEX id_UNIQUE (id ASC) VISIBLE,
      INDEX func_idx ((INET_NTOA(ipaddr4)))
    );

    Забележка: Моля, обърнете внимание на скобите обграждащи израза. Без тях ще има код 1064 (синтактична грешка). Очаквано MySQL Workbench 8.0.13 не е готов за новия синтаксис, така че отворих бъг 92908 и той също вече беше потвърден.

    MySQL Workbench 8.0.13 грешка с функционален индекс

    След това вмъкнах една линия и пуснах обяснение на плана за изпълнение на двете SELECT заявки отдолу:

    INSERT INTO func_index (ipaddr4) VALUES (INET_ATON('192.168.1.1'));

    SELECT * FROM func_index WHERE ipaddr4 = INET_ATON('192.168.1.1');
    SELECT * FROM func_index WHERE INET_NTOA(ipaddr4) = '192.168.1.1';

    Първата ще доведе до пълно сканиране на таблицата, докато втората ще доведе до четене на функционалния индекс func_idx. За да може заявката да използва индекса е важно израза в WHERE клаузата да съвпада с този в дефиницията на индекса.

    Възможност за забрана на създаването на таблици без първичен ключ

    Вече има системна променлива sql_require_primary_key, която предотвратява създаването или промяната на таблици без първичен ключ ако е зададена. В този случай CREATE или ALTER зявки вече ще пропадат с код за грешка 3750 (виж също SQL състояние ER_TABLE_WITHOUT_PK).
    Пробвах тази нова възможност задавайки глобалната променлива в сесия така:

    SET GLOBAL sql_require_primary_key = ON;

    но успях да създам таблица без първичен ключ. Трябва да проверя отново утре и евентуално да докладвам бъг. Задаването на променливата в my.ini и след рестартиране на сървъра работи както трябва:

    SQL> CREATE TABLE tab_no_pk (test INT);
    ERROR: 3750: Unable to create a table without PK, when system variable 'sql_require_primary_key' is set. Add a PK to the table or unset this variable to avoid this message. Note that tables without PK can cause performance problems in row-based replication, so please consult your DBA before changing this setting.

    Вече ще си почина от разработчици незапознати с релационната теория :-)

    Геометрична трансформация

    Вече е възможно да се преобразува геометрия от една пространствена референтна система (SRS) към друга с вградената функция ST_Transform, която приема като втори аргумент целевата SRS. Нямам голям интерес към GIS, така че не съм я пробвал, но изглеждаше, че си струва да бъде спомената.

    Няколко думи за надграждането (от 8.0.12)

    Надграждането на Windows инсталацията ми беше безпроблемно с MySQL Installer за Windows. Обаче, имах проблем с Linux инсталацията ми, която съдържа повече бази данни. Сървъра не можеше да стартира и в журнала с грешки намерих грешки като следната:

    2018-10-23T12:47:49.627732Z 1 [ERROR] [MY-013235] [Server] Error in parsing View 'db'.'aView' during upgrade. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'desc' at line 1

    Отворих бъг 92898 за това и проблема вече беше обяснен от поддръжката с несъвместима промяна (виж SQL Syntax Notes от бележките към версията). Почти сигурен съм, че четох някъде за това, че ASC или DESC ключовите думи за GROUP BY клаузата са обявени за архаични и наистина не помня защо съм използвал такъв синтаксис, но ме свари неподготвен, така че се наложи да надградя заявката на изгледите.

    Има доста повече в MySQL 8.0.13 и горещо ви препоръчвам да прегледате статията The MySQL 8.0.13 Maintenance Release is Generally Available и бележките към версията, след което да изследвате сами.