MariaDB на Google Summer of Code: Итоги GSoC16
Прошлый — 2015-й — GSoC у нас получился очень неудачный. Всего было восемь студентов, но многие провалились еще в середине лета (на midterm evaluation), причем трое были из одного университета в Камеруне (и явно с одного курса), с прекрасными заявками, но они дружно не сделали ничего, от слова «совсем», ну, может одну строчку комментария подправили за полтора месяца. А после провала на midterm они пытались опротестовать наше решение в Google, и даже прислали нам письмо с туманными угрозами. Мол, нехорошо столько студентов проваливать, имидж себе портить, в следующем году Google мест не даст.
Но Google их не послушался и дал. И этот год, наверное по контрасту, получился на редкость удачный.
В этом году нам выделили 10 слотов, на которые претендовали 54 студента. Учитывая печальный опыт прошлого года, мы следили, чтоб все принятые студенты были из разных вузов — если и будут халтурить, то независимо друг от друга. И было как всегда — некоторые студенты писали на наш список рассылки заранее, появлялись на IRC, обсуждали проекты. Два студента даже сделали рабочие прототипы. Такие активные, конечно, все были приняты. Остальные остались за бортом, конкурс был большой. Из принятых десяти студентов получилось, что:
- Один проект был для MHA. MariaDB выступает как «umbrella organization» и проекты могут быть любые в MariaDB/MySQL/Percona экосистеме
- Один проект был для MaxScale, то есть тоже не для сервера. См. выше
- Один студент ничего не делал, и его отсеяли в июле
- С тремя случилось что-то странное — они хорошо работали, проблем не предвиделось. И вдруг уже в конце августа Google написал, что в их данных обнаружились какие-то нестыковки, их заподозрили в обмане и выкинули из программы. Получилось весьма некрасиво, люди старались, работали все лето. Но мы на ситуацию повлиять никак не могли, хотя и пытались.
- Пять студентов прошли до конца и закончили свои проекты. 50% — это для нас большой успех. Про этих пятерых — ниже и подробнее.
Даниил Медведев: NO PAD collations
Как написано в документации (вообще-то мельком и между строк), при сравнении строк разной длины MariaDB Server (и Percona Server и MySQL) дополняют более короткую строку пробелами пока длины не сравняются (то есть, конечно, ничего на самом деле не дополняется, но результат получается такой, как будто бы дополнялось). Выглядит это так:
MariaDB [test]> select "abcd" > "abc";
+----------------+
| "abcd" > "abc" |
+----------------+
| 1 |
+----------------+
1 row in set (0.00 sec)
MariaDB [test]> select "abc\t" > "abc";
+-----------------+
| "abc\t" > "abc" |
+-----------------+
| 0 |
+-----------------+
1 row in set (0.00 sec)
Во втором случае длинная строка лексикографически меньше, чем короткая, потому, что
'\t'
меньше пробела. Это все хорошо и по стандарту. Но не совсем. В SQL-стандарте у collation есть такое свойство, она может быть PADSPACE или NOPAD. В первом случае короткая строка добивается пробелами при сравнении, во втором, соответственно, этого не происходит. Так что получается, что до сих пор все сравнения проводились как будто они PADSPACE. И теперь Даниил Медведев сделал нам NO PAD collations. Эффект не всегда заметен, но он есть: MariaDB [test]> set collation_connection=utf8_general_nopad_ci;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test]> select "abc\t" > "abc";
+-----------------+
| "abc\t" > "abc" |
+-----------------+
| 1 |
+-----------------+
1 row in set (0.00 sec)
Этот проект был полностью закончен и теперь он уже в MariaDB 10.2.2.
Галина Шалыгина: Condition pushdown into non-mergeable views
Галина — не новичок у нас. Она хорошо знакома с одной из самых сложных подсистем MariaDB — оптимизатором запросов и является автором реализации оператора
WITH
, рекурсивных и нерекурсивных CTE.Для этого GSoC Галина выбрала оптимизацию представлений, для которых нельзя сделать слияние с главным запросом. Лучше всего, конечно, выполнять запрос с представлениями методом слияния (view merge), при котором представление подставляется как макрос в главный запрос. Например, такой запрос:
CREATE VIEW v1 AS SELECT a1+b1 AS c1, d1 FROM t1 WHERE e1>5;
SELECT x2 FROM v1, t2 WHERE c1=y2 AND d1=10;
Можно преобразовать в:
SELECT x2 FROM t1, t2 WHERE a1+b1=y2 AND d1=10 AND e1>5 ;
И тут оптимизатор видит все используемые таблицы, все условия, и может выбрать наилучший план. Но не все представления можно оптимизировать таким способом, например, слияние нельзя делать если в представлении используется GROUP BY
. В таких случаях приходится выполнять представление как отдельный запрос, сохраняя результат во временную таблицу, и потом использовать ее в главном запросе. В итоге оптимизатор не видит «всей картины» и вынужден рассматривать оба запроса по отдельности. То есть, в предыдущем примере он бы не смог использовать условие d1=10
при выборке из таблицы t1
.Вот тут и вступает в силу новая оптимизация. Если представление нельзя подставить в главный запрос и оптимизатор вынужден считать его отдельно, используя временную таблицу, он теперь будет пытаться использовать условия из главного запроса при заполнении этой временной таблицы, в некотором роде проталкивая их внутрь представления. То есть, в примере выше оптимизатор сохранит во временной таблице результат такого запроса:
SELECT a1+b1 AS c1, d1 FROM t1 WHERE e1>5 AND d1=10;
Что может ускорить выполнение запроса на порядки.
Эта работа тоже была полностью закончена, и она уже доступна в 10.2.3
Shubham Barai, Sachin Setiya: Arbitrary length UNIQUE constraints
Задача состояла в реализации ограничений уникальности произвольной длины. Обычно, когда кто-то при создании таблицы пишет
UNIQUE(a,b,c)
, MariaDB (и Percona, и MySQL) создают индекс по полям a, b, c. Отсюда и получается ограничение — уникальность комбинации полей можно гарантировать только тогда, когда суммарная длина этих полей не превышает максимальной допустимой длины ключа индекса. Но это же нелогично! «Индекс» — это способ оптимизировать доступ к данным, его вообще нет в SQL-стандарте. А UNIQUE — это логическое ограничение данных, прямо из SQL-стандарта, и совершенно непонятно, почему оно должно страдать из-за каких-то ограничений конкретной реализации ускорения запросов. Наше решение было — создать еще одно поле, скрытое, пользователям его показывать не надо, и писать туда хэш того, для чего делаем UNIQUE. А по этому полю уже можно создать обычный индекс. При записи в нем надо искать, есть ли конфликты, и если есть — вытаскивать конфликтующие записи и уже непосредственно сравнивать значения, хэши могут ведь и случайно совпасть. Более того, MyISAM это уже умеет делать, даже делает — именно так работает SELECT DISTINCT
. Но для пользовательских таблиц это было недоступно. Что и хотелось исправить.С этой задачей возникла сложная ситуация. Мы ее предлагали на GSoC уже несколько лет, никто не хотел. А в этом году на нее пришло две заявки, и обе довольно толковые. Потом оба студента написали работающий (!) прототип — первый раз за мои восемь лет ментором в GSoC. И потом опять же оба (!) заявили, что, мол, это слишком просто, а давай они еще и в InnoDB это сделают. Пришлось брать обоих. Но я одному из них слегка подправил задачу.
Shubham нырнул в дебри InnoDB, и стал разбираться с ее индексами, логами, транзакциями, recovery и прочей магией. А Sachin притворился, что забыл, что внутри MyISAM эта функциональность уже есть, выбросил свой прототип, и начал реализовывать все на уровне сервера, движково-независимым способом. В итоге оба закончили задачу успешно, низкоуровневая реализация чуть-чуть быстрее, а высокоуровневая работает с разными движками, позволяет оптимизатору использовать эти невидимые поля для оптимизации запросов, и приносит с собой прочие интересные плюшки. Мы пока еще до конца не определились, какую берем — все такое вкусное… Так что в 10.2 это уже скорее всего не попадет. Будет в 10.3, наверное.
Sachin Setiya: Hidden columns
А это и есть та вышеупомянутая плюшка. Чтобы сделать невидимое поле (а оно еще и виртуальное, на диск этот хэш можно не писать), мы придумали добавить то, что в Oracle называется Invisible columns, а в DB2 — Implicitly Hidden. Если просто — то при создании поля можно указать, что оно «невидимое». После этого
SELECT *
и INSERT INTO table VALUE (...)
его будут не замечать. Не показывать или, соответственно, не писать в него указанные значения. Но если упомянуть это поле по имени — в любой команде — то оно ведет себя, как обычное видимое поле. Задумывалось это для расширения схемы так, чтобы не сломать работающие (возможно еще и с закрытыми исходниками) приложения. Добавляются скрытые поля, старые запросы их не видят, а новые — называют их по имени, и все работает.Только мы эту идею несколько расширили, добавив разные «уровни невидимости». Первый уровень — как выше, как в Oracle и DB2. Ну, есть еще нулевой — все видно, это неинтересно. Второй уровень — поле видно только в SELECT
и только если явно упомянуть по имени. То есть оно не видно в INSERT
/UPDATE
— его значение нельзя изменить. И оно не видно в CREATE
/ALTER
— его нельзя создать или удалить. Оно создается автоматически. Пример — ROWID и прочие псевдо-поля. И третий уровень, поле не видно вообще нигде. Это как раз и нужно было, чтобы создавать невидимые поля с хэшем. А еще можно их использовать для функциональных индексов типа INDEX(a+b)
— тоже, создаем невидимое виртуальное поле и его индексируем. А еще можно… В общем, когда эта плюшка появилась, идеи себя ждать не заставили.
Как часть предыдущей задачи, это тоже, скорее всего, попадет только в 10.3
Varun Gupta: SQL aggregate functions
Тоже идея, которую обдумывали уже давно. Но толком удалось сформулировать только к этому GSOC-у. В стандарте написано, как создавать пользовательские функции на SQL, и MariaDB это, конечно, умеет:
CREATE FUNCTION COUNT_X(x INT) RETURNS INT
RETURN (SELECT COUNT(*) FROM data_table WHERE value=x);
Но это обычные функции. А вот свои агрегатные функции на SQL создавать нельзя. По крайней мере в стандарте этого нет. В Oracle есть, и в PostgreSQL есть, и, например, в HSQLDB тоже. Мне хотелось, чтоб и в MariaDB было. Долго думали над синтаксисом — стандарта же нет, у всех по-своему. Сравнивали как у других. В итоге, решили не отличаться, и сделали как у всех — то есть, по-своему. Конечно же, наш вариант естественней и проще всего. Наверно те, кто придумывали этот синтаксис в Oracle/PostgreSQL/HSQLDB тоже считают, что их вариант лучший.
Будет работать так:
CREATE AGGREGATE FUNCTION agg_and(x INT) RETURNS INT
BEGIN
DECLARE z INT DEFAULT 65536;
DECLARE CONTINUE HANDLER FOR NOT FOUND RETURN z;
LOOP
FETCH GROUP NEXT ROW;
SET z= (z&x);
END LOOP;
END
Для сравнения, чтоб понять откуда ноги растут, вот абсолютно по стандарту написанная не агрегатная функция, которая вычисляет AND всех значений какого-то столбца в таблице:
CREATE FUNCTION col_and() RETURNS INT
BEGIN
DECLARE x INT;
DECLARE z INT DEFAULT 65536;
DECLARE CONTINUE HANDLER FOR NOT FOUND RETURN z;
DECLARE cur CURSOR FOR SELECT col FROM data_table;
OPEN cur;
LOOP
FETCH cur INTO x;
SET z= (z&x);
END LOOP;
END
То есть, логика та же самая, просто в агрегатную функию подается специальный курсор который проходит про всем значениям группы.
К сожалению, это тоже вряд ли уже попадет в 10.2. Хотя проект тоже закончен практически полностью.
В этом году не было, почему-то, ни одного проекта, законченного, ну, скажем, на три четверти. Так чтобы и бросить жалко и нести неудобно. Все, что хотели, было сделано. И это здорово!
Ждем следующего года…