Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

GA

ラベル MySQL Shell の投稿を表示しています。 すべての投稿を表示
ラベル MySQL Shell の投稿を表示しています。 すべての投稿を表示

2024/01/12

MySQL ShellのloadDumpが MySQL Error 1449 (HY000): The user specified as a definer ('xxx'@'%') does not exist で転けたり転けなかったりする

 

TL;DR

  • DEFINERが存在しないSQL SECURITY DEFINERなビューと、そのビューを使ったビューがある時にMySQL ShellのloadDumpが転けることも転けないこともある
    • mysqldumpからのリストアは100%転ける
  • 転けるのが正しい気がするけど何故か転けずに完了してしまうのが気になったのでこのエントリ

参考: 日々の覚書: MySQLの論理バックアップにおける2段階のViewのリストア

下準備。
ビューのDEFINERになっているアカウントをDROPして、「DEFINERが存在しないビュー」「そのビューを使ったビュー」を作る。
(先にアカウントを作ってからDROPしないと、「DEFINERが存在しないビュー」までは無理矢理作れるけれども「そのビューを使ったビュー」が作れない)

CREATE USER dummy;
CREATE DATABASE d1;
CREATE definer=dummy VIEW d1.v1 AS SELECT 1;
CREATE VIEW d1.v2 AS SELECT * FROM d1.v1;
DROP USER dummy;
SHOW WARNINGS;

DROP USERした時にちゃんとワーニングは出る。

mysql> SHOW WARNINGS;
+---------+------+----------------------------------------------------------------+
| Level   | Code | Message                                                        |
+---------+------+----------------------------------------------------------------+
| Warning | 4005 | User 'dummy'@'%' is referenced as a definer account in a view. |
+---------+------+----------------------------------------------------------------+
1 row in set (0.00 sec)

比較のためにmysqldumpも取っておく。ちなみに --single-transaction を省くと個別にLOCK TABLEしようとして転ける。

$ mysqldump -h172.17.0.2 -B d1 --single-transaction > /tmp/d1.sql

このダンプをリストアしようとすると必ず転ける。

$ mysql -h172.17.0.3 -vv < /tmp/d1.sql
..
--------------
/*!50001 CREATE ALGORITHM=UNDEFINED */
/*!50013 DEFINER=`root`@`%` SQL SECURITY DEFINER */
/*!50001 VIEW `v2` AS select `v1`.`1` AS `1` from `v1` */
--------------

ERROR 1449 (HY000) at line 85: The user specified as a definer ('dummy'@'%') does not exist
Bye

MySQL ShellでdumpInstanceする。

$ mysqlsh -h172.17.0.2 -- util dumpInstance '/tmp/dump'
Please provide the password for 'root@172.17.0.2':
Save password for 'root@172.17.0.2'? [Y]es/[N]o/Ne[v]er (default No):
Acquiring global read lock
Global read lock acquired
Initializing - done
1 out of 5 schemas will be dumped and within them 0 tables, 2 views.
3 out of 6 users will be dumped.
Gathering information - done
All transactions have been started
Locking instance for backup
Global read lock has been released
Writing global DDL files
Writing users DDL
Running data dump using 4 threads.
NOTE: Progress information uses estimated values and may not be accurate.
Writing schema metadata - done
Writing DDL - done
Writing table metadata - done
Starting data dump
?% (0 rows / ?), 0.00 rows/s, 0.00 B/s uncompressed, 0.00 B/s compressed
Dump duration: 00:00:00s
Total duration: 00:00:00s
Schemas dumped: 1
Tables dumped: 0
Uncompressed data size: 0 bytes
Compressed data size: 0 bytes
Compression ratio: 0.0
Rows written: 0
Bytes written: 0 bytes
Average uncompressed throughput: 0.00 B/s
Average compressed throughput: 0.00 B/s

この時、 /tmp/dump/d1.json に入っている views の順番も多少関係する。v1が先に来ていれば転ける確率の方が高くなり、v2が先に来ていれば転けない可能性の方が高かった。

$ cat /tmp/dump/d1.json
{
    "schema": "d1",
    "includesDdl": true,
    "includesViewsDdl": true,
    "includesData": true,
    "tables": [],
    "views": [
        "v2",
        "v1"
    ],
    "events": [],
    "functions": [],
    "procedures": [],
    "basenames": {
        "v2": "d1@v2",
        "v1": "d1@v1"
    }
}

--threads を大きめにすると転ける可能性が高くなる。

転けない時はこんな感じ。

$ mysqlsh -h172.17.0.4 -- util loadDump '/tmp/dump' --threads=10
Please provide the password for 'root@172.17.0.2':
Save password for 'root@172.17.0.2'? [Y]es/[N]o/Ne[v]er (default No):
Loading DDL and Data from '/tmp/dump' using 10 threads.
Opening dump...
Target is MySQL 8.0.35. Dump was produced from MySQL 8.0.35
Scanning metadata - done
Checking for pre-existing objects...
Executing common preamble SQL
Executing DDL - done
Executing view DDL - done
Executing common postamble SQL
Starting data load
?% (0 bytes / ?), 0.00 B/s, 0 / 0 tables done
Recreating indexes - done
No data loaded.
0 warnings were reported during the load.

転ける時はこんな感じ。

$ mysqlsh -h172.17.0.5 -- util loadDump '/tmp/dump' --threads=20
Please provide the password for 'root@172.17.0.2':
Save password for 'root@172.17.0.2'? [Y]es/[N]o/Ne[v]er (default No):
Loading DDL and Data from '/tmp/dump' using 20 threads.
Opening dump...
Target is MySQL 8.0.35. Dump was produced from MySQL 8.0.35
Scanning metadata - done
Checking for pre-existing objects...
Executing common preamble SQL
Executing DDL - done
ERROR: Error executing DDL script for view `d1`.`v2`: MySQL Error 1449 (HY000): The user specified as a definer ('dummy'@'%') does not exist: /*!50001 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `v2` AS select `v1`.`1` AS `1` from `v1` */
Executing view DDL - done
ERROR: The user specified as a definer ('dummy'@'%') does not exist

ビューを2段階リストアしているので、mysqldumpとかで直列にやった場合はこうなる。

CREATE VIEW v1 AS SELECT 1 AS 1;
CREATE VIEW v2 AS SELECT 1 AS 1;
DROP VIEW v1;
CREATE DEFINER=dummy VIEW v1 AS SELECT 1;
DROP VIEW v2;
CREATE VIEW v2 AS SELECT * FROM v1;    -- ここでER_NO_SUCH_USER(=1449)で転ける

しかしパラレルにリストアすると、まれに v2 と v1 のタイミングがひっくり返る。MySQL Shellはd1.jsonのviewsに順番に読もうとする(んだと思う)ので、v2が先に来ているとよくこの順番になるんではないか。

CREATE VIEW v1 AS SELECT 1 AS 1;
CREATE VIEW v2 AS SELECT 1 AS 1;
DROP VIEW v2;
CREATE VIEW v2 AS SELECT * FROM v1;    -- この時点ではv1は不正なビューではないので転けない

DROP VIEW v1;

CREATE DEFINER=dummy VIEW v1 AS SELECT 1;  -- 不正なビューだがワーニング止まりなので転けはしない

なので、転けたり転けなかったりするという違いが生まれている模様。
たぶん、動作としては常に転けるのが正しいとは思う。

2022/03/15

MySQL Shellのプロンプトをゼロからカスタマイズしてみんとす(変数の存在によって表示させるclassを変えるとか)

TL;DR


謎だったのだ。パッケージに一緒についてくる /usr/share/mysqlsh/prompt/ あたりに転がっているprompt.jsonのこのへんの記述。

  "segments": [
..
    {
      "classes": ["noschema%schema%", "schema"],
      "bg": 242,
      "fg": 15,
      "shrink": "ellipsize",
      "weight": -1,
      "padding" : 1
    },
..

https://github.com/mysql/mysql-shell/blob/8.0.28/samples/prompt/prompt_256pl.json#L103

↓こんな風に、スキーマがセットされてる時は灰色の、そうでない時はそもそもこのラベルそのものが表示されないような感じ。

しかし noschema と schema はクラスとして定義されているが、 noschemad1 なんてクラスは定義されていないし、とするとこのクラス名の中の %schema% はスキーマ名として展開される組み込み変数とは違うのか…? とか思ってたら、READMEに書いてあった。

README最高だな ドキュメントに書けよ 。

https://github.com/mysql/mysql-shell/blob/master/samples/prompt/README.prompt#L94-L96

クラスのリスト(たとえこの空振りを利用した打ち分けをしない場合でも、リストに入れないといけない)は、最初に存在した1つが採用される。

さっきのスキーマの例でいくと、d1スキーマがカレントスキーマの間は noschemad1 クラスを探して存在せず、 schema クラスにフォールバックする。
カレントスキーマが存在しない間は noschema クラスが存在するのでそっちを採用する。

というわけで、prompt.jsonをこんな風にしてみた。

{
  "classes":
  {
    "schema_mysql":
    {
      "text": "[[ !!SYSTEM_SCHEMA!! ]]",
      "fg": "white",
      "bg": "red"
    },
    "noschema":
    {
      "text": "[[ no database selected ]]"
    },
    "default_schema":
    {
      "text": "[[ %schema% ]]",
      "fg": "#b0c4de"
    }
  },
  "segments":
  [
    { "classes": ["noschema%schema%", "schema_%schema%", "default_schema"] }
  ],
  "prompt": { "text": "> " }
}

まあ取り敢えず、思ったようには動いているようだ( %schema% が空の時は “noschema” にマッチ、 mysqlの時は “schema_mysql” にマッチ、それ以外の時は “default_schema” にフォールバック)

variablesが matchif_trueif_false の2分割しかできないから、3つ以上の分岐をしたい時にはこっちのが良いのかな、と思いつつ。

2022/02/28

MySQL Shellのプロンプトをゼロからカスタマイズしてみんとす(classとsegment)

TL;DR


前回作ったところから。


$ mysqlsh -S /usr/mysql/8.0.28/data/mysql.sock -uroot --database d1

root@150-95-141-50 [d1] (READ_WRITE)>

root@150-95-141-50 [d1] (READ_WRITE)> SHOW TABLES;
SyntaxError: Unexpected identifier

( ゚д゚) そう、何も指定していないのでこれはJavaScriptモードなのである。

メインで使う時はSQLモードなので、「SQLモードの時はこのまま、それ以外のモードの時はなんか出したい」と思う。

$ cat ~/.mysqlsh/prompt.json
{
  "variables":
  {
    "read_only_str":
    {
      "match":
      {
        "pattern": "OFF",
        "value": "%sysvar:read_only%"
      },
      "if_true": "READ_WRITE",
      "if_false": "READ_ONLY"
    },
    "base_prompt":
    {
      "match":
      {
        "pattern": "",
        "value": ""
      },
      "if_true": "%user%@%sysvar:hostname% [%schema%] (%read_only_str%)"
    },
    "mode_based_prompt":
    {
      "match":
      {
        "pattern": "SQL",
        "value": "%Mode%"
      },
      "if_true": "%base_prompt%> ",
      "if_false": "%base_prompt%\n%Mode%> "
    }
  },
  "prompt": { "text": "%mode_based_prompt%" }
}

$ mysqlsh -S /usr/mysql/8.0.28/data/mysql.sock -uroot --database d1
root@150-95-141-50 [d1] (READ_WRITE)
JS> \py
Switching to Python mode...
root@150-95-141-50 [d1] (READ_WRITE)
Py> \sql
Switching to SQL mode... Commands end with ;
Fetching table and column names from `d1` for auto-completion... Press ^C to stop.
root@150-95-141-50 [d1] (READ_WRITE)>

うん、想像通りに動いてはいそう。
はい次、 READ_ONLY の場合は赤文字とかにしたい。

$ cat ~/.mysqlsh/prompt.json
{
  "variables":
  {
    "read_only_str":
    {
      "match":
      {
        "pattern": "OFF",
        "value": "%sysvar:read_only%"
      },
      "if_true": "READ_WRITE",
      "if_false": "READ_ONLY"
    },
    "read_only_color":
    {
      "match":
      {
        "pattern": "OFF",
        "value": "%sysvar:read_only%"
      },
      "if_true": "green",
      "if_false": "red"
    },
    "base_prompt":
    {
      "match":
      {
        "pattern": "",
        "value": ""
      },
      "if_true": "%user%@%sysvar:hostname% [%schema%] (%read_only_str%)"
    },
    "mode_based_prompt":
    {
      "match":
      {
        "pattern": "SQL",
        "value": "%Mode%"
      },
      "if_true": "%base_prompt%> ",
      "if_false": "%base_prompt%\n%Mode%> "
    }
  },
  "prompt": { "text": "%mode_based_prompt%", "fg": "%read_only_color%" }
}

長くなってきた…。

$ mysqlsh -S /usr/mysql/8.0.28/data/mysql.sock -uroot --database d1 --sqlc

Error loading prompt theme '/home/yoku0825/.mysqlsh/prompt.json': Error loading prompt theme: Invalid color value %read_only_color%

あれ、食えない…。
固定文字列で “red” とか “green” って入れると通るんだけどなあ。

色々納得はいかないけれど、何パターンかトライアンドエラーで頑張ってようやく classsegment の招待に行きついた。

classは俺が思っているclassとはちょっと違って textfg とかの要素を全部持たせてしまうものみたいな気がする(他のサンプルを見るに)

ということは、表示用の要素がそのまま詰まった変数みたいなものってことになる。まあ、そういうものだと思うことにしよう。。

そして segments の方は、理屈はわからないけれどどうも「 segments に列挙されたものを順番に並べて、最後に prompt に指定されたものを添えることでプロンプトを作る」みたいになっている。

たとえば segments から先だけを

  "segments":
  [
    { "classes": ["%read_only_str%"] },
    { "text": "%mode_based_prompt%" },
    { "text": "hogehoge" },
  ],
  "prompt": { "text": "pro" }

みたいにすると、

$ mysqlsh -S /usr/mysql/8.0.28/data/mysql.sock -uroot --database d1 --sqlc
READ_ONLY root@150-95-141-50 [d1] (READ_ONLY)>  hogehogepro

みたいになる。

こういうものだとわかれば、まあ、

  • class に表示したいものを詰める
  • segments にそれを列挙する
  • 今までさんざん頑張ってきたけど、 prompt はたぶんおまけ
    こういう風になるだろうか…。

話がひっくり返るけれど、 segments の中で class を展開するには

    { "classes": ["%read_only_str%"] },

こんな風にすると、カスタム変数 "class": "read_only_str" に対応する要素が展開される、っぽい。

$ cat ~/.mysqlsh/prompt.json
{
  "variables":
  {
    "read_only_str":
    {
      "match":
      {
        "pattern": "OFF",
        "value": "%sysvar:read_only%"
      },
      "if_true": "READ_WRITE",
      "if_false": "READ_ONLY"
    },
    "base_prompt":
    {
      "match":
      {
        "pattern": "",
        "value": ""
      },
      "if_true": "%user%@%sysvar:hostname% [%schema%] "
    },
    "mode_based_prompt":
    {
      "match":
      {
        "pattern": "SQL",
        "value": "%Mode%"
      },
      "if_true": "%base_prompt% ",
      "if_false": "%base_prompt%\n%Mode% "
    }
  },
  "classes":
  {
    "READ_ONLY":
    {
      "text": "(READ_ONLY)",
      "fg": "red"
    },
    "READ_WRITE":
    {
      "text": "(READ_WRITE)",
      "fg": "green"
    }
  },
  "segments":
  [
    { "text": "%mode_based_prompt%" },
    { "classes": ["%read_only_str%"] },
  ],
  "prompt": { "text": "> " }
}

というわけでこうすると

こうなった。

2022/02/27

MySQL Shellのプロンプトをゼロからカスタマイズしてみんとす(サーバー変数&カスタム変数を使ってみる)

TL;DR


取り敢えず、 mysqlsh が最初に食おうとするプロンプト用のJSONファイルは ~/.mysqlsh/prompt.json, それが無ければデフォルトというかビルトインのJSONファイルのパスを探して(termの色数とか勘案しつつ)開くっぽい。


$ strace -f mysqlsh 2>&1 | grep json

open("/home/yoku0825/.mysqlsh/options.json", O_RDONLY) = 3

[pid 10699] stat("/home/yoku0825/.mysqlsh/prompt.json", 0x7fffae727880) = -1 ENOENT (No such file or directory)

[pid 10699] open("/usr/share/mysqlsh/prompt/prompt_256.json", O_RDONLY) = 4

カスタマイズしたいので、とりあえず ~/.mysqlsh/prompt.json を作ることにする。


$ touch /home/yoku0825/.mysqlsh/prompt.json

$ mysqlsh

Error loading prompt theme '/home/yoku0825/.mysqlsh/prompt.json': Can't parse ''
mysql-js>

JSONとしてパースできなさそうなファイルを作るとフォールバックする。

mysql-shell/prompt_nocolor.json at master · mysql/mysql-shell など参考にすると、おそらく最小のコンフィグは↓のような感じ。

$ cat ~/.mysqlsh/prompt.json
{
  "prompt": { "text": "mysqlsh> " }
}

$ mysqlsh

mysqlsh>

OK, 固定文字列はこれで出せる。

mysql コマンドでいう \h\u みたいな シーケンス はREADMEに Supported variables are: で書かれているやつで、 % でくくられたやつ。

ざっと user@hostname [database]> みたいなやつにするにはこうかな。

$ cat ~/.mysqlsh/prompt.json
{
  "prompt": { "text": "%user%@%host% [%schema%]> " }
}

$ mysqlsh
@ []>

接続してないから軒並み空っぽになってしまった。。
ちゃんと mysqld に接続すれば出る。

$ mysqlsh -S /usr/mysql/8.0.28/data/mysql.sock -uroot --database d1
root@localhost [d1]>

root@localhost [d1]> \use mysql
Default schema set to `mysql`.
root@localhost [mysql]>

OK。
ところでこの %host% は飽くまでも mysqlsh から見て指定した接続先ホストで、localhostになっている(これは mysql\h も同じ)

俺はこれが嫌いで、 mysql コマンドでも「クライアントから見た先じゃなくてサーバーサイドから取得したホスト名を表示したい」みたいなパッチも投げているくらいなので、ここをカスタマイズしたい。

サーバーサイドのホスト名は SHOW GLOBAL VARIABLES LIKE 'hostname' で取れて、MySQL Shellのプロンプトには SHOW GLOBAL VARIABLES の値を取ってくるための %sysvar:*% パラメーターがあるので、これを使ってみる。

$ cat ~/.mysqlsh/prompt.json
{
  "prompt": { "text": "%user%@%sysvar:hostname% [%schema%]> " }
}

$ mysqlsh -S /usr/mysql/8.0.28/data/mysql.sock -uroot --database d1

root@150-95-141-50 [d1]>

よし行けた。

ついでによく見たくなる、「そのホストが read_only かどうか」も積んでみる。

$ cat ~/.mysqlsh/prompt.json
{
  "prompt": { "text": "%user%@%sysvar:hostname% [%schema%] (%sysvar:read_only%)> " }
}

$ mysqlsh -S /usr/mysql/8.0.28/data/mysql.sock -uroot --database d1
root@150-95-141-50 [d1] (OFF)>

0か1になると思ったら案外OFF表記だった。
このままだと何が何だかわからないので、 read_only だったら「READ_ONLY」、そうでなければ「READ_WRITE」とか表示させたい。

カスタム変数とかいうのがあるのでそれを使う。

$ cat ~/.mysqlsh/prompt.json
{
  "variables":
  {
    "read_only_str":
    {
      "match":
      {
        "pattern": "OFF",
        "value": "%sysvar:read_only%"
      },
      "if_true": "READ_WRITE",
      "if_false": "READ_ONLY"
    }
  },

  "prompt": { "text": "%user%@%sysvar:hostname% [%schema%] (%read_only_str%)> " }
}

パッと他のサンプルのJSONファイルを覗いて見るに、条件分岐に使えるのは match しかなさそう。
取り敢えず read_only_str というカスタム変数を、 OFF にマッチしたら READ_WRITE, マッチしなければ READ_ONLY にセットする。
prompt からは %read_only_str% を呼び出すようにしてトライ。

$ mysqlsh -S /usr/mysql/8.0.28/data/mysql.sock -uroot --database d1

root@150-95-141-50 [d1] (READ_WRITE)>

上手くいった。
ところでこれ、途中で値を変えたらどうなるんだろ。

root@150-95-141-50 [d1] (READ_WRITE)> \sql SET GLOBAL read_only = 1;
Fetching table and column names from `d1` for auto-completion... Press ^C to stop.
Query OK, 0 rows affected (0.0003 sec)
root@150-95-141-50 [d1] (READ_ONLY)>

おお、読み直した。
他のセッションから値を変えたらどうかな?

mysql80 22> SET GLOBAL read_only = OFF;  -- 別ターミナルのmysqlコマンドラインクライアントから
Query OK, 0 rows affected (0.00 sec)

root@150-95-141-50 [d1] (READ_ONLY)>  
root@150-95-141-50 [d1] (READ_WRITE)>   /* 空Enter打ったタイミングで変わった */
root@150-95-141-50 [d1] (READ_WRITE)>

プロンプトを描画するたびに毎回フェッチしてるっぽい。
よしよし、良いんではないか。

と思いつつ今日はここまで。


【2022/03/03 00:38】

続き -> 日々の覚書: MySQL Shellのプロンプトをゼロからカスタマイズしてみんとす(classとsegment)

2020/08/31

MySQL InnoDB Cluster/ReplicaSet 8.0.21で「mysqlrouterから参照されないように」設定する

TL;DR


まずはフツーにMySQL Shellでサンドボックスを3つばかり作る。

$ mysqlsh -- dba deploySandboxInstance 3306 { --password="" }
$ mysqlsh -- dba deploySandboxInstance 3307 { --password="" }
$ mysqlsh -- dba deploySandboxInstance 3308 { --password="" }

3306のインスタンスで dba.createaReplicaSet('myRs')

$ mysqlsh --uri=root:""@localhost:3306 -- dba createReplicaSet myRs

--replicasetrs に受けてからの rs.addInstance
(このコマンドラインオプションの奇妙な書き方は 日々の覚書: CentOS 7のAMIでEC2を起動してGroup Replicationを組むところまでを何も考えずに をやってる時に見つけた)

$ mysqlsh --uri=root:""@localhost:3306 --replicaset -- rs addInstance root@localhost:3307 { --recoveryMethod=clone }
$ mysqlsh --uri=root:""@localhost:3306 --replicaset -- rs addInstance root@localhost:3308 { --recoveryMethod=clone }

以前にInnoDB Clusterとかを組んだことがあると mysqlrouter.conf に書かれていたりするので一度消してから mysqlrouter --bootstrap

$ sudo rm /etc/mysqlrouter/mysqlrouter.conf
$ sudo mysqlrouter --bootstrap=root@localhost:3306 --user=mysqlrouter
Please enter MySQL password for root:
$ sudo systemctl start mysqlrouter

この状態でRead-Onlyな6447は3307と3308にラウンドロビンされている。

$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3308
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3308
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3308

rs.setInstanceOption(‘127.0.0.1:3308’, ‘tag:_hidden’, true) とやると

$ mysqlsh --uri=root:""@localhost:3306 --replicaset -- rs setInstanceOption '127.0.0.1:3308' 'tag:_hidden' 'true'
WARNING: Using a password on the command line interface can be insecure.
You are connected to a member of replicaset 'myRs'.

ルーターを再起動とかしなくても読み取り分散から外れる。

$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307
$ mysql -h127.0.0.1 -P6447 -sse "SELECT @@port"
3307

これはうまく使えば便利そう。


ただし、_hidden: trueのままでもsetPrimaryInstanceできてしまうし、_hiddenは効いたままなのでその辺はどうなのか感ある。