MySQL 8.0 вводит частичное обновление значений JSON, которое является хорошим улучшением производительности для приложений, которые часто обновляет небольшие порции больших JSON документов. Перед тем, как вы сделали изменения в хранимом документ JSON, полный новый JSON документ будет записан в базу данных, даже если обновление только изменило несколько байт в документе. Что нового в MySQL 8.0, является то, что MySQL способен признать, что некоторые заявления UPDATE могут изменить JSON документы на месте, и для этих заявлений будет обеспечивать механизм хранения с патчами, которые описывают изменения. Механизм хранения может использовать эти участки, чтобы написать минимальный объем данных. Патчи также используются при репликации строк на основе, чтобы уменьшить количество двоичного журнала отгруженного к ведомой репликации, если надлежащая опция включена. Это может привести к значительному снижению дисковых операций ввода/вывода и сети ввода/вывода для обновления рабочих нагрузок.
Как это использовать
Итак, что вы должны сделать, чтобы получить преимущество частичного обновления значений JSON в вашем приложении? Во многих случаях ничего, кроме обновления до MySQL 8.0. Новый синтаксис для частичного обновления отсутствует. Вместо этого MySQL анализирует каждый оператор UPDATE и включает его, когда это возможно, таким образом, ваше приложение выиграет от улучшений автоматически, если операторы UPDATE уже находятся на форме, которая распознана как частичное обновление.
Простой пример-следующая инструкция, которая изменяет атрибут name в значении JSON:
UPDATE t SET json_col = JSON_SET(json_col, '$.name', 'Mash') WHERE id = 123
Это обновление может быть выполнено как частичное обновление, поскольку оно принимает столбец JSON (json_col), изменяет один из его членов, а затем сохраняет его в том же столбце, из которого он считывает (json_col). Кроме того, обновляемое значение JSON уже должно иметь член с именем name, и в значении JSON должно быть достаточно места для хранения нового имени. Если значение JSON обновляется что-то вроде {“name”: “Andr”, …}, механизм хранения будет сказано, что он может просто перезаписать четыре байта в строке “Andr“ четырьмя байтами ” Knut”, а остальная часть значения может быть оставлена нетронутой.
Если значение JSON обновляется уже содержал более длинное название, как и {"name": "Oleg", ...}, обновление до сих пор может быть выполнена в виде частичного обновления. В этом случае двигатель хранения будет велено переписать первые четыре байта «Oleg» на «Mash», и перезаписывает длину байт имя атрибута со значением 4, чтобы соответствовать новой длине.
Если же, с другой стороны, исходное значение в формате JSON содержало более короткое название, как и {"name": "Anna", ...}, то не было бы достаточно места для нового имени. В этом случае обновление вернется к выполнению полного обновления, а это значит, что он записывает все новое значение в формате JSON в базу данных. Аналогичным образом, если исходное значение JSON не имеет атрибута name вообще, полное обновление будет выполнено, чтобы освободить место для нового атрибута.
Частичное обновление не ограничивается функцией JSON_SET. Оно также может быть использован с функциями JSON_REPLACE и JSON_REMOVE. Вы даже можете объединить эти функции, до тех пор, пока столбец ввода такой же, что и столбец назначения. И вы можете объединить частичное обновление столбцов в формате JSON с помощью обычных обновлений других столбцов в том же заявлении. Оба эти утверждения UPDATE попытается частично обновить столбец JSON:
UPDATE t SET json_col = JSON_SET(json_col, '$.name', UPPER(json_col->>'$.name')), json_col = JSON_REPLACE(json_col, '$.age', int_col), json_col = JSON_REMOVE(json_col, '$.address'), int_col = int_col + 1; UPDATE t SET json_col = JSON_REPLACE(JSON_REMOVE(json_col, '$.address'), '$.name', UPPER(json_col->>'$.name'), '$.age', int_col), int_col = int_col + 1;
влияние на производительность
Давайте посмотрим, как это влияет на производительность при обновлении некоторых больших значений в формате JSON. Во-первых, давайте создадим таблицу с некоторыми большими документами:
CREATE TABLE t(id INT PRIMARY KEY AUTO_INCREMENT, json_col JSON, name VARCHAR(100) AS (json_col->>'$.name'), age INT AS (json_col->'$.age')); INSERT INTO t(json_col) VALUES (JSON_OBJECT('name', 'AndreyEx', 'age', 24, 'data', REPEAT('x', 10 * 1000 * 1000))), (JSON_OBJECT('name', 'Oleg', 'age', 32, 'data', REPEAT('y', 10 * 1000 * 1000))), (JSON_OBJECT('name', 'Mash', 'age', 40, 'data', REPEAT('z', 10 * 1000 * 1000))), (JSON_OBJECT('name', 'Alex', 'age', 27, 'data', REPEAT('w', 10 * 1000 * 1000))); INSERT INTO t(json_col) SELECT json_col FROM t; INSERT INTO t(json_col) SELECT json_col FROM t;
Это создает таблицу JSON с 16 документами, где каждый документ занимает примерно 10Мб.
Теперь мы попробуем увеличить атрибут age в каждом из документов с MySQL 5.7:
mysql> update t set json_col = json_set(json_col, '$.age', age + 1); Query OK, 16 rows affected (13.56 sec) Rows matched: 16 Changed: 16 Warnings: 0
Если мы попробуем тот же оператор UPDATE с MySQL 8.0, мы видим, что он во много раз быстрее:
mysql> update t set json_col = json_set(json_col, '$.age', age + 1); Query OK, 16 rows affected (2.94 sec) Rows matched: 16 Changed: 16 Warnings: 0
Хотя это намного быстрее, три секунды для обновления нескольких байтов в 16 документах все еще не очень быстро. Причина состоит в том, что MySQL 8.0 имеет двоичное журналирование, включенное по умолчанию, также в нереплицируемых средах. Это означает, что оба перед изображением и после изображения больших значений JSON записываются в двоичный журнал. Как упоминалось ранее, была добавлена опция, чтобы двоичный журнал содержал патчи вместо полных документов JSON, когда у нас есть частичные обновления. Давайте попробуем включить эту опцию:
mysql> SET binlog_row_value_options = PARTIAL_JSON, binlog_row_image = MINIMAL; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> update t set json_col = json_set(json_col, '$.age', age + 1); Query OK, 16 rows affected (0.23 sec) Rows matched: 16 Changed: 16 Warnings: 0
Это больше похоже на правду. Оператор UPDATE, который занял приблизительно 13.5 секунды с MySQL 5.7, тратит только 3 секунды с MySQL 8.0 в его конфигурации по умолчанию. И с небольшим количеством дополнительной настройки, мы получаем еще до примерно четверти секунды. Это более чем в 50 раз быстрее.
Вывод
MySQL 8.0 ускоряет некоторые обновления значений в формате JSON, если обновления изменяют небольшие части больших документов в формате JSON с помощью функции JSON_SET, JSON_REPLACE и JSON_REMOVE. Ускорение можно увидеть из коробки. Если двоичная регистрация включена, есть варианты, чтобы сделать бинарный журнал содержащий только измененные части значений в формате JSON для дальнейшего улучшения производительности обновления.
Более подробную информацию можно найти в частичном обновлений значений JSON в разделе справочного руководства.