GA

2023/08/01

gtid_mode=OFFの移行元MySQLからGroupReplicationにマイグレーションするはなし

  • origin側はgtid_mode=OFF, binlog_format=MIXEDでこれを変えてはいけない
  • MySQLはこのケースに限り 8.0.28 以外でも良い
  • メンテナンスには入れられる。ただし、メンテナンスウィンドウ内でMyDumperをかけられるほどデータは小さくない
  • なおGroupReplication ≠ InnoDB Clusterとした。つまりMySQL Shellの支援とMySQL Routerプロセスは使わない
  • シングルプライマリーモード。MySQL Routerは使わないけど到達性の問題は 俺以外の誰か が何とかするものとする(実際、何とかしてくれた)

まずはフツーに空っぽの状態でGroupReplicationを組む。

これはほぼGetting Startedの通りにいった気がする(3か月くらい前なので既にやや記憶が曖昧)

深く考えずにデータを引っこ抜いて、GroupReplicationのPRIMARYノードに入れる。

Non-GTID環境からGTID環境への非同期レプリケーションは8.0.23でサポートされていたので、遠慮なくそれを使う。

GroupReplication側から

CHANGE REPLICATION SOURCE TO source_host = 'origin_replication_source', source_log_file= .., source_log_pos = .., ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTION = local FOR CHANNEL 'migration' ;

本当は(?) gtid_executedをすっきりさせたかったので group_replication_replication_name を割り当てたかったんだけど、それをやろうとするとError 4021で怒られた。

ERROR 4021 (HY000): CHANGE MASTER TO ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS = <UUID> cannot be executed because the UUID value is equal to the group_replication_group_name.

local だと自分(GroupReplication PRIMARY)の server_uuid を割り当ててくれる。まあそのへんのマシンで uuidgen して持ってきても大して変わらない。

ともあれこれでレプリケーションを開始…

ERROR 3098 (HY000): The table does not comply with the requirements by external plugin

早速レプリケーション止まった…(´・ω・`)

原因は2つ、HealthCheckに使っていたMEMORYストレージエンジン(GroupReplicationはInnoDBのテーブル以外は更新できない)と、Primary KeyのないInnoDBテーブル(GroupReplicationはPKもしくはPKE.. Every table that is to be replicated by the group must have a defined primary key, or primary key equivalent where the equivalent is a non-null unique key. .. つまり全カラムがNOT NULLかつUNIQUE制約がついているインデックスが無いとエラーで更新を拒否する。

なおこの2つは同じエラーを返すので、実際はエラーになったSQLスレッドのエラーを見てどっちが原因かを判断する。

MySQL error code MY-003098 (ER_BEFORE_DML_VALIDATION_ERROR): The table does not comply with the requirements by an external plugin.

この2つ(Non-InnoDBテーブルをすべてGR側だけでもInnoDB化し、PKが無いテーブルにすべてPKをつける)が終わればようやくレプリケーションが開始できるが、今回は残念ながら新たにPKを足すという選択肢が無かったので

MySQL :: MySQL 8.0 Reference Manual :: 13.1.20.11 Generated Invisible Primary Keys

MySQL 8.0.30とそれ以降のコイツを使わせてもらうことにした。
基本的にはGR側にデータを突っ込む時だけで良いはずだけれど、過渡期(Asyncレプリケーションを組んだままの期間)が長くてorigin側にPKのないテーブルを追加で作られてしまいそうな場合は CHANGE REPLICATION SOURCE TO に一ひねり加える。

CHANGE REPLICATION SOURCE TO .., REQUIRE_TABLE_PRIMARY_KEY_CHECK = GENERATE;

REQUIRE_TABLE_PRIMARY_KEY_CHECK = GENERATEにして初めて、「originではPKが無かったものをレプリケーションの途中で横取りしてGIPKを足す」になる。これを忘れるとPKが無いままGR側にやってきてまた3098の洗礼を食らうので注意。

(なお、過渡期の間にそんなテーブルをCREATEするな、というネゴシエーションがとれるならこれは別に要らない。正直検証はしたけど、その期間にPKなしでCREATE TABLEされたテーブルなどなかった)

で、取り敢えず頭の図の状態には持って行けたが、もうひと悶着あったのでそれは次回にでも。

2023/05/08

MySQL 8.0でtx_isolationを使わせるようにするパッチ

TL;DR

  • ちょっと試してみただけなのでフツーに使うことはまずない。自分ですら使おうと思っていない。

パッチはこれだけ。

$ diff ./sql/sys_vars.cc.orig ./sql/sys_vars.cc
5196a5197,5204
> // NO_CMD_LINE - different name of the option
> static Sys_var_transaction_isolation Sys_tx_isolation(
>        "tx_isolation", "Default transaction isolation level."
>        "This variable is deprecated and will be removed in a future release.",
>        UNTRACKED_DEFAULT SESSION_VAR(transaction_isolation), NO_CMD_LINE,
>        tx_isolation_names, DEFAULT(ISO_REPEATABLE_READ), NO_MUTEX_GUARD,
>        NOT_IN_BINLOG, ON_CHECK(check_transaction_isolation));
>

両方使える ( tx_isolation がdeprecatedで transaction_isolation が推奨 ) MySQL 5.7.42の記述はこんな感じ。

https://github.com/mysql/mysql-server/blob/mysql-5.7.42/sql/sys_vars.cc#L4221-L4238

型としては Sys_var_tx_isolation で受けて、他のコードでトランザクション分離レベルを参照する時も thd->tx_isolation とか thd->variables.tx_isolation で参照している。

MySQL 8.0ではこれを逆に tx_isolationSys_var_transaction_isolation 型に受けるようにすれば良さそうな気がした。

https://github.com/mysql/mysql-server/blob/mysql-8.0.33/sql/sys_vars.cc#L5197-L5202

言うても thd->tx_isolationtx_isolation のままっぽい。

https://github.com/mysql/mysql-server/blob/mysql-8.0.33/sql/sql_class.cc#L1087

一方で、 substitute の属性はなくなっているのでdeprecated warningとかを出すのはそのままでは無理な気がするけど試してないし気にしていない。

https://github.com/mysql/mysql-server/blob/mysql-5.7.42/sql/sys_vars.h#L2100-L2116

https://github.com/mysql/mysql-server/blob/mysql-8.0.33/sql/sys_vars.h#L2343-L2354


というわけで

mysql80 9> SELECT @@version, @@tx_isolation;
+--------------+-----------------+
| @@version    | @@tx_isolation  |
+--------------+-----------------+
| 8.0.33-debug | REPEATABLE-READ |
+--------------+-----------------+
1 row in set (0.00 sec)


出来上がりはしたけど、使わずに済むことを祈る。

2023/05/04

DATETIME型と現在時刻の差分が秒数でほしい時に"-"で比較してはいけない

TL;DR

  • TIMESTAMPDIFF を使う
  • 知ってたはずなのにやらかしたので自戒を込めてメモ

mysql80 65> CREATE TABLE t11 (dt DATETIME);
Query OK, 0 rows affected (0.14 sec)

mysql80 65> INSERT INTO t11 VALUES (NOW());
Query OK, 1 row affected (0.05 sec)

mysql80 65> SELECT * FROM t11;
+---------------------+
| dt                  |
+---------------------+
| 2023-05-04 06:23:00 |
+---------------------+
1 row in set (0.01 sec)

この時刻との差が(整数の)秒が欲しいからと言って、

mysql80 65> SELECT NOW(), dt, NOW() - dt FROM t11;
+---------------------+---------------------+------------+
| NOW()               | dt                  | NOW() - dt |
+---------------------+---------------------+------------+
| 2023-05-04 06:23:10 | 2023-05-04 06:23:00 |         10 |
+---------------------+---------------------+------------+
1 row in set (0.00 sec)

こうしてはいけない。

mysql80 65> SELECT NOW(), dt, NOW() - dt FROM t11;  -- 期待したのは70
+---------------------+---------------------+------------+
| NOW()               | dt                  | NOW() - dt |
+---------------------+---------------------+------------+
| 2023-05-04 06:24:10 | 2023-05-04 06:23:00 |        110 |
+---------------------+---------------------+------------+
1 row in set (0.01 sec)

NOW() - dtCAST(NOW() AS SIGNED) - CAST(dt AS SIGNED) に変換されるので、 20230504062410 - 20230504062300 になるのでMySQL的には整数の110を返す。

基本、秒単位でしか離れないところで、分をまたいだ時だけ値がジャンプしてて変だなと思うまで思い出さなかった。反省。

正しくはTIMESTAMPDIFFでこう(名前からして、引数をUNIXTIMEにしないといけないかと思って雑にやったのが良くない…)

mysql80 65> SELECT NOW(), dt, NOW() - dt, TIMESTAMPDIFF(SECOND, dt, NOW()) FROM t11;
+---------------------+---------------------+------------+----------------------------------+
| NOW()               | dt                  | NOW() - dt | TIMESTAMPDIFF(SECOND, dt, NOW()) |
+---------------------+---------------------+------------+----------------------------------+
| 2023-05-04 06:26:51 | 2023-05-04 06:23:00 |        351 |                              231 |
+---------------------+---------------------+------------+----------------------------------+
1 row in set (0.00 sec)

2023/04/21

MySQL 8.0.32にデフォルトコレーションをutf8mb4_general_ciにするパッチを当てる

地味にgrepから始める。

$ grep -r 'utf8mb4_0900_ai_ci' | grep -v mysql-test | grep -v scripts/fill_help_tables.sql | grep -v gunit

client/mysqltest.cc:    &my_charset_utf8mb4_0900_ai_ci; /* Default charset */
cmake/character_sets.cmake:  SET(DEFAULT_COLLATION "utf8mb4_0900_ai_ci")
include/m_ctype.h:extern MYSQL_PLUGIN_IMPORT CHARSET_INFO my_charset_utf8mb4_0900_ai_ci;
man/myisamchk.1:Character set:       utf8mb4_0900_ai_ci (255)
mysys/charset-def.cc:extern CHARSET_INFO my_charset_utf8mb4_0900_ai_ci;
mysys/charset-def.cc:  add_compiled_collation(&my_charset_utf8mb4_0900_ai_ci);
plugin/x/src/mysql_variables.cc:  return &my_charset_utf8mb4_0900_ai_ci;
router/src/routing/src/sql_lexer.cc:            if (underscore_cs == &my_charset_utf8mb4_0900_ai_ci) {
router/src/routing/src/sql_lexer_thd.h:        &my_charset_utf8mb4_0900_ai_ci};
share/messages_to_clients.txt:  eng "Invalid default collation %s: utf8mb4_0900_ai_ci or utf8mb4_general_ci expected"
share/messages_to_error_log.txt:  eng "Invalid default collation %s: utf8mb4_0900_ai_ci or utf8mb4_general_ci expected"
sql/create_field.cc:  if (!has_explicit_collation && fld_charset == &my_charset_utf8mb4_0900_ai_ci)
sql/dd/info_schema/show.cc:      &my_charset_utf8mb4_0900_ai_ci) {
sql/dd/info_schema/show.cc:      &my_charset_utf8mb4_0900_ai_ci) {
sql/dd/info_schema/show.cc:    // ... WHEN <ID of utf8mb4_0900_ai_ci> ...
sql/dd/info_schema/show.cc:        new (mem_root) Item_uint(my_charset_utf8mb4_0900_ai_ci.number);
sql/dd/info_schema/show.cc:    //   WHEN <ID of utf8mb4_0900_ai_ci> THEN FALSE
sql/gis/st_units_of_measure.cc:      &my_charset_utf8mb4_0900_ai_ci, PSI_INSTRUMENT_ME);
sql/histograms/equi_height_bucket.cc:  strings "110", "120" and "130" in utf8mb4_0900_ai_ci gives us the following
sql/item_geofunc.cc:                              &my_charset_utf8mb4_0900_ai_ci,
sql/mysqld.cc:      &my_charset_utf8mb4_0900_ai_ci;
sql/parse_tree_nodes.cc:    if (cs2 == &my_charset_utf8mb4_0900_ai_ci &&
sql/server_component/mysql_string_service.cc:  return to_api(&my_charset_utf8mb4_0900_ai_ci);
sql/sql_db.cc:        create_info->default_table_charset == &my_charset_utf8mb4_0900_ai_ci)
sql/sql_lex.cc:            if (underscore_cs == &my_charset_utf8mb4_0900_ai_ci) {
sql/sql_show.cc:        create.default_table_charset == &my_charset_utf8mb4_0900_ai_ci) {
sql/sql_show.cc:          (field->charset() == &my_charset_utf8mb4_0900_ai_ci &&
sql/sql_show.cc:           share->table_charset != &my_charset_utf8mb4_0900_ai_ci)) {
sql/sql_show.cc:            share->table_charset == &my_charset_utf8mb4_0900_ai_ci) {
sql/sql_table.cc:        create_info->default_table_charset == &my_charset_utf8mb4_0900_ai_ci) {
sql/sys_vars.cc:  if (cs == &my_charset_utf8mb4_0900_ai_ci ||
sql/sys_vars.cc:    DEFAULT(&my_charset_utf8mb4_0900_ai_ci), NO_MUTEX_GUARD, IN_BINLOG,
storage/perfschema/pfs_name.cc:const CHARSET_INFO *PFS_routine_name::m_cs = &my_charset_utf8mb4_0900_ai_ci;
strings/ctype-uca.cc:CHARSET_INFO my_charset_utf8mb4_0900_ai_ci = {

ちくちくとutf8mb4_0900_ai_ciをutf8mb4_general_ciと入れ替える作業をした。

$ diff -r mysql-8.0.32.orig mysql-8.0.32

diff -r mysql-8.0.32.orig/client/mysqltest.cc mysql-8.0.32/client/mysqltest.cc
344c344
<     &my_charset_utf8mb4_0900_ai_ci; /* Default charset */
---
>     &my_charset_utf8mb4_general_ci; /* Default charset */
diff -r mysql-8.0.32.orig/cmake/character_sets.cmake mysql-8.0.32/cmake/character_sets.cmake
29c29
<   SET(DEFAULT_COLLATION "utf8mb4_0900_ai_ci")
---
>   SET(DEFAULT_COLLATION "utf8mb4_general_ci")
diff -r mysql-8.0.32.orig/plugin/x/src/mysql_variables.cc mysql-8.0.32/plugin/x/src/mysql_variables.cc
39c39
<   return &my_charset_utf8mb4_0900_ai_ci;
---
>   return &my_charset_utf8mb4_general_ci;
diff -r mysql-8.0.32.orig/router/src/routing/src/sql_lexer_thd.h mysql-8.0.32/router/src/routing/src/sql_lexer_thd.h
58c58
<         &my_charset_utf8mb4_0900_ai_ci};
---
>         &my_charset_utf8mb4_general_ci};
diff -r mysql-8.0.32.orig/sql/create_field.cc mysql-8.0.32/sql/create_field.cc
214c214
<   if (!has_explicit_collation && fld_charset == &my_charset_utf8mb4_0900_ai_ci)
---
>   if (!has_explicit_collation && fld_charset == &my_charset_utf8mb4_general_ci)
diff -r mysql-8.0.32.orig/sql/dd/info_schema/show.cc mysql-8.0.32/sql/dd/info_schema/show.cc
84c84
<       &my_charset_utf8mb4_0900_ai_ci) {
---
>       &my_charset_utf8mb4_general_ci) {
104c104
<     // ... 'utf8mb4_general_ci' ...
---
>     // ... 'utf8mb4_0900_ai_ci' ...
106c106
<         Item_string(STRING_WITH_LEN("utf8mb4_general_ci"), system_charset_info);
---
>         Item_string(STRING_WITH_LEN("utf8mb4_0900_ai_ci"), system_charset_info);
210c210
<       &my_charset_utf8mb4_0900_ai_ci) {
---
>       &my_charset_utf8mb4_general_ci) {
234c234
<     // ... WHEN <ID of utf8mb4_0900_ai_ci> ...
---
>     // ... WHEN <ID of utf8mb4_general_ci> ...
236c236
<         new (mem_root) Item_uint(my_charset_utf8mb4_0900_ai_ci.number);
---
>         new (mem_root) Item_uint(my_charset_utf8mb4_general_ci.number);
diff -r mysql-8.0.32.orig/sql/gis/st_units_of_measure.cc mysql-8.0.32/sql/gis/st_units_of_measure.cc
30c30
<       &my_charset_utf8mb4_0900_ai_ci, PSI_INSTRUMENT_ME);
---
>       &my_charset_utf8mb4_general_ci, PSI_INSTRUMENT_ME);
diff -r mysql-8.0.32.orig/sql/item_geofunc.cc mysql-8.0.32/sql/item_geofunc.cc
5279c5279
<                               &my_charset_utf8mb4_0900_ai_ci,
---
>                               &my_charset_utf8mb4_general_ci,
diff -r mysql-8.0.32.orig/sql/mysqld.cc mysql-8.0.32/sql/mysqld.cc
5089c5089
<       &my_charset_utf8mb4_0900_ai_ci;
---
>       &my_charset_utf8mb4_general_ci;
diff -r mysql-8.0.32.orig/sql/parse_tree_nodes.cc mysql-8.0.32/sql/parse_tree_nodes.cc
236c236
<     if (cs2 == &my_charset_utf8mb4_0900_ai_ci &&
---
>     if (cs2 == &my_charset_utf8mb4_general_ci &&
diff -r mysql-8.0.32.orig/sql/server_component/mysql_string_service.cc mysql-8.0.32/sql/server_component/mysql_string_service.cc
62c62
<   return to_api(&my_charset_utf8mb4_0900_ai_ci);
---
>   return to_api(&my_charset_utf8mb4_general_ci);
diff -r mysql-8.0.32.orig/sql/sql_db.cc mysql-8.0.32/sql/sql_db.cc
301c301
<         create_info->default_table_charset == &my_charset_utf8mb4_0900_ai_ci)
---
>         create_info->default_table_charset == &my_charset_utf8mb4_general_ci)
diff -r mysql-8.0.32.orig/sql/sql_lex.cc mysql-8.0.32/sql/sql_lex.cc
1538c1538
<             if (underscore_cs == &my_charset_utf8mb4_0900_ai_ci) {
---
>             if (underscore_cs == &my_charset_utf8mb4_general_ci) {
diff -r mysql-8.0.32.orig/sql/sql_show.cc mysql-8.0.32/sql/sql_show.cc
1336c1336
<         create.default_table_charset == &my_charset_utf8mb4_0900_ai_ci) {
---
>         create.default_table_charset == &my_charset_utf8mb4_general_ci) {
2025,2026c2025,2026
<           (field->charset() == &my_charset_utf8mb4_0900_ai_ci &&
<            share->table_charset != &my_charset_utf8mb4_0900_ai_ci)) {
---
>           (field->charset() == &my_charset_utf8mb4_general_ci &&
>            share->table_charset != &my_charset_utf8mb4_general_ci)) {
2357c2357
<             share->table_charset == &my_charset_utf8mb4_0900_ai_ci) {
---
>             share->table_charset == &my_charset_utf8mb4_general_ci) {
diff -r mysql-8.0.32.orig/sql/sql_table.cc mysql-8.0.32/sql/sql_table.cc
8410c8410
<         create_info->default_table_charset == &my_charset_utf8mb4_0900_ai_ci) {
---
>         create_info->default_table_charset == &my_charset_utf8mb4_general_ci) {
diff -r mysql-8.0.32.orig/sql/sys_vars.cc mysql-8.0.32/sql/sys_vars.cc
7193c7193
<     DEFAULT(&my_charset_utf8mb4_0900_ai_ci), NO_MUTEX_GUARD, IN_BINLOG,
---
>     DEFAULT(&my_charset_utf8mb4_general_ci), NO_MUTEX_GUARD, IN_BINLOG,
diff -r mysql-8.0.32.orig/storage/perfschema/pfs_name.cc mysql-8.0.32/storage/perfschema/pfs_name.cc
61c61
< const CHARSET_INFO *PFS_routine_name::m_cs = &my_charset_utf8mb4_0900_ai_ci;
---
> const CHARSET_INFO *PFS_routine_name::m_cs = &my_charset_utf8mb4_general_ci;
diff -r mysql-8.0.32.orig/strings/ctype-uca.cc mysql-8.0.32/strings/ctype-uca.cc
9568c9568
<     MY_CS_UTF8MB4_UCA_FLAGS | MY_CS_PRIMARY, /* state    */
---
>     MY_CS_UTF8MB4_UCA_FLAGS,                 /* state    */
diff -r mysql-8.0.32.orig/strings/ctype-utf8.cc mysql-8.0.32/strings/ctype-utf8.cc
7766c7766
<         MY_CS_UNICODE_SUPPLEMENT, /* state  */
---
>         MY_CS_UNICODE_SUPPLEMENT | MY_CS_PRIMARY , /* state  */

で、コンパイル

$ cmake3 -DWITH_BOOST=~/mysql-8.0.32/boost ~/mysql-8.0.32
$ make -j8
mysql> CREATE DATABASE d1;
Query OK, 1 row affected (0.00 sec)

mysql> SHOW CREATE DATABASE d1;
+----------+------------------------------------------------------------------------------------------------------------------------------+
| Database | Create Database                                                                                                              |
+----------+------------------------------------------------------------------------------------------------------------------------------+
| d1       | CREATE DATABASE `d1` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ |
+----------+------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SHOW COLLATION LIKE 'utf8mb4_general_ci';
+--------------------+---------+----+---------+----------+---------+---------------+
| Collation          | Charset | Id | Default | Compiled | Sortlen | Pad_attribute |
+--------------------+---------+----+---------+----------+---------+---------------+
| utf8mb4_general_ci | utf8mb4 | 45 | Yes     | Yes      |       1 | PAD SPACE     |
+--------------------+---------+----+---------+----------+---------+---------------+
1 row in set (0.01 sec)

Vanilla版ではutf8mb4_general_ciのDefaultはEmptyだけど、パッチ版はYesになってる。

mysql80 8> SHOW COLLATION LIKE 'utf8mb4_general_ci';
+--------------------+---------+----+---------+----------+---------+---------------+
| Collation          | Charset | Id | Default | Compiled | Sortlen | Pad_attribute |
+--------------------+---------+----+---------+----------+---------+---------------+
| utf8mb4_general_ci | utf8mb4 | 45 |         | Yes      |       1 | PAD SPACE     |
+--------------------+---------+----+---------+----------+---------+---------------+
1 row in set (0.01 sec)

ちょっと使ってみよう。

2023/04/01

MySQL 8.0で導入された動的権限を利用してたけのこ派にCOUNTを使えなくする

TL;DR

  • 今日は2023年4月1日ですし、私はたけのこ派です

動的権限とは、MySQL 8.0で加わった「 mysql.user やその他のテーブルに独自のカラムを持た ない タイプの権限」のことらしい。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 6.2.2 MySQL で提供される権限

↓このへんが「静的権限」で

mysql> DESC mysql.user;
+--------------------------+-----------------------------------+------+-----+-----------------------+-------+
| Field                    | Type                              | Null | Key | Default               | Extra |
+--------------------------+-----------------------------------+------+-----+-----------------------+-------+
| Host                     | char(255)                         | NO   | PRI |                       |       |
| User                     | char(32)                          | NO   | PRI |                       |       |
| Select_priv              | enum('N','Y')                     | NO   |     | N                     |       |
| Insert_priv              | enum('N','Y')                     | NO   |     | N                     |       |
| Update_priv              | enum('N','Y')                     | NO   |     | N                     |       |
| Delete_priv              | enum('N','Y')                     | NO   |     | N                     |       |
| Create_priv              | enum('N','Y')                     | NO   |     | N                     |       |
| Drop_priv                | enum('N','Y')                     | NO   |     | N                     |       |
| Reload_priv              | enum('N','Y')                     | NO   |     | N                     |       |
| Shutdown_priv            | enum('N','Y')                     | NO   |     | N                     |       |
| Process_priv             | enum('N','Y')                     | NO   |     | N                     |       |
| File_priv                | enum('N','Y')                     | NO   |     | N                     |       |
| Grant_priv               | enum('N','Y')                     | NO   |     | N                     |       |
| References_priv          | enum('N','Y')                     | NO   |     | N                     |       |
| Index_priv               | enum('N','Y')                     | NO   |     | N                     |       |
| Alter_priv               | enum('N','Y')                     | NO   |     | N                     |       |
| Show_db_priv             | enum('N','Y')                     | NO   |     | N                     |       |
| Super_priv               | enum('N','Y')                     | NO   |     | N                     |       |
| Create_tmp_table_priv    | enum('N','Y')                     | NO   |     | N                     |       |
| Lock_tables_priv         | enum('N','Y')                     | NO   |     | N                     |       |
| Execute_priv             | enum('N','Y')                     | NO   |     | N                     |       |
| Repl_slave_priv          | enum('N','Y')                     | NO   |     | N                     |       |
| Repl_client_priv         | enum('N','Y')                     | NO   |     | N                     |       |
| Create_view_priv         | enum('N','Y')                     | NO   |     | N                     |       |
| Show_view_priv           | enum('N','Y')                     | NO   |     | N                     |       |
| Create_routine_priv      | enum('N','Y')                     | NO   |     | N                     |       |
| Alter_routine_priv       | enum('N','Y')                     | NO   |     | N                     |       |
| Create_user_priv         | enum('N','Y')                     | NO   |     | N                     |       |
| Event_priv               | enum('N','Y')                     | NO   |     | N                     |       |
| Trigger_priv             | enum('N','Y')                     | NO   |     | N                     |       |
| Create_tablespace_priv   | enum('N','Y')                     | NO   |     | N                     |       |
| ssl_type                 | enum('','ANY','X509','SPECIFIED') | NO   |     |                       |       |
| ssl_cipher               | blob                              | NO   |     | NULL                  |       |
| x509_issuer              | blob                              | NO   |     | NULL                  |       |
| x509_subject             | blob                              | NO   |     | NULL                  |       |
| max_questions            | int unsigned                      | NO   |     | 0                     |       |
| max_updates              | int unsigned                      | NO   |     | 0                     |       |
| max_connections          | int unsigned                      | NO   |     | 0                     |       |
| max_user_connections     | int unsigned                      | NO   |     | 0                     |       |
| plugin                   | char(64)                          | NO   |     | caching_sha2_password |       |
| authentication_string    | text                              | YES  |     | NULL                  |       |
| password_expired         | enum('N','Y')                     | NO   |     | N                     |       |
| password_last_changed    | timestamp                         | YES  |     | NULL                  |       |
| password_lifetime        | smallint unsigned                 | YES  |     | NULL                  |       |
| account_locked           | enum('N','Y')                     | NO   |     | N                     |       |
| Create_role_priv         | enum('N','Y')                     | NO   |     | N                     |       |
| Drop_role_priv           | enum('N','Y')                     | NO   |     | N                     |       |
| Password_reuse_history   | smallint unsigned                 | YES  |     | NULL                  |       |
| Password_reuse_time      | smallint unsigned                 | YES  |     | NULL                  |       |
| Password_require_current | enum('N','Y')                     | YES  |     | NULL                  |       |
| User_attributes          | json                              | YES  |     | NULL                  |       |
+--------------------------+-----------------------------------+------+-----+-----------------------+-------+
51 rows in set (0.01 sec)

↓このへんが「動的権限」

mysql> SELECT * FROM global_grants;
+------------------+-----------+------------------------------+-------------------+
| USER             | HOST      | PRIV                         | WITH_GRANT_OPTION |
+------------------+-----------+------------------------------+-------------------+
| mysql.infoschema | localhost | AUDIT_ABORT_EXEMPT           | N                 |
| mysql.infoschema | localhost | FIREWALL_EXEMPT              | N                 |
| mysql.infoschema | localhost | SYSTEM_USER                  | N                 |
| mysql.session    | localhost | AUDIT_ABORT_EXEMPT           | N                 |
| mysql.session    | localhost | AUTHENTICATION_POLICY_ADMIN  | N                 |
| mysql.session    | localhost | BACKUP_ADMIN                 | N                 |
| mysql.session    | localhost | CLONE_ADMIN                  | N                 |
| mysql.session    | localhost | CONNECTION_ADMIN             | N                 |
| mysql.session    | localhost | FIREWALL_EXEMPT              | N                 |
| mysql.session    | localhost | PERSIST_RO_VARIABLES_ADMIN   | N                 |
| mysql.session    | localhost | SESSION_VARIABLES_ADMIN      | N                 |
| mysql.session    | localhost | SYSTEM_USER                  | N                 |
| mysql.session    | localhost | SYSTEM_VARIABLES_ADMIN       | N                 |
| mysql.sys        | localhost | AUDIT_ABORT_EXEMPT           | N                 |
| mysql.sys        | localhost | FIREWALL_EXEMPT              | N                 |
| mysql.sys        | localhost | SYSTEM_USER                  | N                 |
| root             | localhost | APPLICATION_PASSWORD_ADMIN   | Y                 |
| root             | localhost | AUDIT_ABORT_EXEMPT           | Y                 |
| root             | localhost | AUDIT_ADMIN                  | Y                 |
| root             | localhost | AUTHENTICATION_POLICY_ADMIN  | Y                 |
| root             | localhost | BACKUP_ADMIN                 | Y                 |
| root             | localhost | BINLOG_ADMIN                 | Y                 |
| root             | localhost | BINLOG_ENCRYPTION_ADMIN      | Y                 |
| root             | localhost | CLONE_ADMIN                  | Y                 |
| root             | localhost | CONNECTION_ADMIN             | Y                 |
| root             | localhost | ENCRYPTION_KEY_ADMIN         | Y                 |
| root             | localhost | FIREWALL_EXEMPT              | Y                 |
| root             | localhost | FLUSH_OPTIMIZER_COSTS        | Y                 |
| root             | localhost | FLUSH_STATUS                 | Y                 |
| root             | localhost | FLUSH_TABLES                 | Y                 |
| root             | localhost | FLUSH_USER_RESOURCES         | Y                 |
| root             | localhost | GROUP_REPLICATION_ADMIN      | Y                 |
| root             | localhost | GROUP_REPLICATION_STREAM     | Y                 |
| root             | localhost | INNODB_REDO_LOG_ARCHIVE      | Y                 |
| root             | localhost | INNODB_REDO_LOG_ENABLE       | Y                 |
| root             | localhost | KINOKO_ADMIN                 | Y                 |
| root             | localhost | PASSWORDLESS_USER_ADMIN      | Y                 |
| root             | localhost | PERSIST_RO_VARIABLES_ADMIN   | Y                 |
| root             | localhost | REPLICATION_APPLIER          | Y                 |
| root             | localhost | REPLICATION_SLAVE_ADMIN      | Y                 |
| root             | localhost | RESOURCE_GROUP_ADMIN         | Y                 |
| root             | localhost | RESOURCE_GROUP_USER          | Y                 |
| root             | localhost | ROLE_ADMIN                   | Y                 |
| root             | localhost | SENSITIVE_VARIABLES_OBSERVER | Y                 |
| root             | localhost | SERVICE_CONNECTION_ADMIN     | Y                 |
| root             | localhost | SESSION_VARIABLES_ADMIN      | Y                 |
| root             | localhost | SET_USER_ID                  | Y                 |
| root             | localhost | SHOW_ROUTINE                 | Y                 |
| root             | localhost | SYSTEM_USER                  | Y                 |
| root             | localhost | SYSTEM_VARIABLES_ADMIN       | Y                 |
| root             | localhost | TABLE_ENCRYPTION_ADMIN       | Y                 |
| root             | localhost | TAKENOKO_ADMIN               | Y                 |
| root             | localhost | XA_RECOVER_ADMIN             | Y                 |
+------------------+-----------+------------------------------+-------------------+
53 rows in set (0.00 sec)

(つд⊂)ゴシゴシ

mysql> SELECT * FROM global_grants WHERE priv IN ('KINOKO_ADMIN', 'TAKENOKO_ADMIN');
+------+-----------+----------------+-------------------+
| USER | HOST      | PRIV           | WITH_GRANT_OPTION |
+------+-----------+----------------+-------------------+
| root | localhost | KINOKO_ADMIN   | Y                 |
| root | localhost | TAKENOKO_ADMIN | Y                 |
+------+-----------+----------------+-------------------+
2 rows in set (0.00 sec)

  , .
(;゚ Д゚) …!?

mysql> CREATE USER yoku0825;
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT ALL ON d1.* TO yoku0825;
Query OK, 0 rows affected (0.00 sec)

(;゚д゚)ゴクリ…

mysql> SHOW GRANTS;
+--------------------------------------------------+
| Grants for yoku0825@%                            |
+--------------------------------------------------+
| GRANT USAGE ON *.* TO `yoku0825`@`%`             |
| GRANT ALL PRIVILEGES ON `d1`.* TO `yoku0825`@`%` |
+--------------------------------------------------+
2 rows in set (0.00 sec)

mysql> CREATE DATABASE d1;
Query OK, 1 row affected (0.00 sec)

mysql> CREATE TABLE d1.t1 (num int);
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO d1.t1 VALUES (1);
Query OK, 1 row affected (0.01 sec)

mysql> SELECT COUNT(*) FROM d1.t1;
ERROR 1227 (42000): Access denied; you need (at least one of) the KINOKO_ADMIN privilege(s) for this operation

キタ━━━━(゚∀゚)━━━━!!

mysql> GRANT KINOKO_ADMIN ON *.* TO yoku0825;
Query OK, 0 rows affected (0.01 sec)

(;゚д゚)ゴクリ…

mysql> SHOW GRANTS;
+--------------------------------------------------+
| Grants for yoku0825@%                            |
+--------------------------------------------------+
| GRANT USAGE ON *.* TO `yoku0825`@`%`             |
| GRANT KINOKO_ADMIN ON *.* TO `yoku0825`@`%`      |
| GRANT ALL PRIVILEGES ON `d1`.* TO `yoku0825`@`%` |
+--------------------------------------------------+
3 rows in set (0.00 sec)

mysql> SELECT COUNT(*) FROM d1.t1;
+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
1 row in set (0.00 sec)

キタ━━━━(゚∀゚)━━━━!!

今回当てたパッチを当てたファイルは2つ。

$ diff ./sql/auth/dynamic_privileges_impl.cc{.orig,}
249a250,253
>       ret += service->register_privilege(
>           STRING_WITH_LEN("KINOKO_ADMIN"));
>        ret += service->register_privilege(
>           STRING_WITH_LEN("TAKENOKO_ADMIN"));

動的権限の定義をするファイルのこのへん ( https://github.com/mysql/mysql-server/blob/mysql-8.0.32/sql/auth/dynamic_privileges_impl.cc#L250 ) と

$ diff ./sql/opt_sum.cc{.orig,}
392a393,401
>           // We have to check the user is Kinoko or Takenoko first.
>           Security_context *sctx = thd->security_context();
>           if (!(sctx->has_global_grant(STRING_WITH_LEN("KINOKO_ADMIN")).first))
>           {
>             my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "KINOKO_ADMIN");
>             return false;
>           }
>           // End
>

COUNT 関数の時に必ず通ってそうなここ ( https://github.com/mysql/mysql-server/blob/mysql-8.0.32/sql/opt_sum.cc#L393 )

ホントはきのことたけのこを排他にしたくてこのへん ( https://github.com/mysql/mysql-server/blob/mysql-8.0.32/sql/parse_tree_helpers.cc#L339 ) にこんなのも書いてたんだけど

// Check Kinoko vs Takenoko
if (strcmp(s, "KINOKO_ADMIN") || strcmp(s, "TAKENOKO_ADMIN"))
{
  Security_context *sctx = thd->security_context();

  if ((sctx->has_global_grant(STRING_WITH_LEN("KINOKO_ADMIN")).first &&
       strcmp(s, "TAKENOKO_ADMIN")) ||
      (sctx->has_global_grant(STRING_WITH_LEN("TAKENOKO_ADMIN")).first &&
       strcmp(s, "KINOKO_ADMIN")))
  {
    my_error(ER_FEATURE_UNSUPPORTED, MYF(0),
             "having both of KINOKO_ADMIN & TAKENOKO_ADMIN",
             "you have to choise only one of KINOKO xor TAKENOKO");
  }
}

これだとSecurity_contextはGRANTを実行したアカウントのものになっちゃって、適用先アカウントがきのこかたけのこかを取るのは面倒そうだったので諦めた。

今までの権限システムだとカラム増やさないといけなくて大変そうだったのが、単純に定義だけ追加すれば良い(あるいはプラグイン側からも追加できる様子)だけなので楽で良いですね。

それでは良いたけのこライフを。俺はたけのこ派です。