- 投稿日:2020-01-14T15:06:38+09:00
カラム名からテーブルを探す
例えばusersテーブルというユーザーを入れたテーブルがあったとして、usersテーブルを変更する場合、それと関連するuser_idカラムを持ったテーブルを探したいときがあると思います。
その時は
select table_name, column_name from information_schema.columns where column_name = 'user_id' and table_schema = 'DB名';とすることで、user_idカラムを持つテーブル名の一覧を取得することができます。
- 投稿日:2020-01-14T14:14:08+09:00
AWS Cloud9 へ mysql2 のgemを導入する時にエラーが出てなんとか解決した
Rubyの学習でデータベースを使おうと思って、
データベースといえばMySQLと思いつきでインストールしてしまい、
そのまま使えないじゃん!って思い、
mysql2というgem使えば使えるのか!と調べて気づき
mysql2がエラー出て入らねぇよ!!!と困り、
google大先生とQiitaの各先人様のおかげで導入できた
この流れの記録を書いておきます。
先に結論から
https://qiita.com/higeo_kk/items/828d9f11cdc69c85c078
この方の記事通りでさくっと解決してしまった・・・
ruby-develがなかったのかな?
develってことはディベロッパーツール?
今は理解できてないけどこの辺は環境構築で絶対にハマるところだと
私の未熟な勘でも感じているので、また後日解明したい。内容は、mysql2の導入エラー
まぁ困った
実際にはこんな感じ
ec2-user:/ $ sudo gem install mysql2 Building native extensions. This could take a while... ERROR: Error installing mysql2: ERROR: Failed to build gem native extension. /usr/bin/ruby2.0 extconf.rb mkmf.rb can't find header files for ruby at /usr/share/ruby/include/ruby.h Gem files will remain installed in /usr/local/share/ruby/gems/2.0/gems/mysql2-0.5.3 for inspection. Results logged to /usr/local/share/ruby/gems/2.0/gems/mysql2-0.5.3/ext/mysql2/gem_make.outそもそもまずこれやってる時点の私はCUIが苦手で、
エラーに書いてあるディレクトリまで移動してもあまり意味わかっておらず、
usr/local/share/の中にrubyフォルダ無かったりと散々でしたw
- 投稿日:2020-01-14T13:17:02+09:00
リカバリー用SQLをさくっと準備してからDBデータを一括更新する
何かしらサービスを運営していると、「特定のデータを一括で変更して欲しい」というような、データ運用上の要望がしばしば発生する。そういう時はたいていデータ管理用のGUIツール等では対応できない、もしくは手動運用では手間がかかり過ぎておよそ現実的でない、というようなケースであることが多く、エンジニアがデータベースのデータを直接更新する必要が出てくるのだ。
(本音としては、データベースに慣れている私でも、商用DBで直接UPDATEクエリを実行するとかやりたくないんだよ。マジで。まぁ、やらざるを得ないからやるけどさ…w)
閑話休題、ここからが本題である。
DBの特定テーブルのカラム単位で一括データ更新を行う際に、リカバリー用のSQLをさくっと準備して、「できる限り心穏やかに」データ更新ができる方法を紹介してみようかと。まぁ、そこまで頻度が高くない運用シーンなので、毎回うろ覚えになっていた手順を自分用の備忘録としてまとめておこうかと思って書いた記事でもある。
SELECT結果を外部ファイルに出力する
まず基本知識として、SQLのSELECTクエリの結果を外部ファイルとして出力する方法を知らねばならない。私が得意なデータベースはMySQLとPostgreSQLなので、その二つのDBMSでのやり方を紹介する。
MySQLでは、
mysqlSELECT * FROM post_master INTO OUTFILE 'output_my.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '"' ESCAPED BY '"' LINES TERMINATED BY '\n';MySQLではGLOBALオプション
secure_file_privで指定されているパスに対してのみファイルの入出力が許可されるため、このオプションがNULLになっているとそもそも外部ファイル出力は行えない。また、このオプションは読み込み専用でSET GLOBALでは設定変更できないため、my.cnf等の設定ファイルで指定して再読み込みさせる必要がある(MySQLの再起動が必要)。
[mysqld] secure-file-priv = ""
テスト環境とかなら、上記設定を追加した設定ファイルを/etc/my.cnf等に設置してMySQLを再起動すればOKだ。商用環境ならば、DB構築時にサーバ上のセキュアなパスを指定しておくことが望ましい。PostgreSQLでは、
psqlCOPY post_master TO 'output_pg.csv' WITH CSV DELIMITER ',' FORCE QUOTE * NULL AS '';スーパーユーザじゃなかったり、pg_write_server_files権限がないユーザで実行すると下記のようにエラーになるので注意が必要だ。
ERROR: must be superuser or a member of the pg_write_server_files role to COPY to a file HINT: Anyone can COPY to stdout or from stdin. psql's \copy command also works for anyone.
そんな時は、一旦DBコンソールから抜けて、コマンドラインからpsql -c '\copy ...'コマンドを使用すればCSV出力ができる。
例えば、上記のCOPYクエリの場合なら、
psql -U ユーザ名 -c "\copy post_master TO 'output_pg.csv' WITH CSV DELIMITER ',' FORCE QUOTE * NULL AS '';" データベース名
─とやれば良い。ただ、これだとテーブル単位で全データを出力することになるので、利用シーンによっては都合が悪いこともある。
実例がある方が理解しやすいので、サンプルテーブル(テーブル名は
post_master)を準備して外部ファイル出力をやってみよう。
post_id post_type author_id title content regist_date limit_date status シーケンシャルな整数値 (PrimaryKey) 文字列 整数値 文字列 文字列 DATE型 DATE型 真偽値(0か1) 上記のようなテーブル(MySQLとPostgreSQLにそれぞれ同じテーブルを作成した)には、Fakerでダミーデータを1000行ほど入れた状態だ(入れたデータもそれぞれ同じだ)。このテーブルに対して、
limit_date(論理値としては、このテーブルデータの「有効期限」みたいな意味付けとする)の日付が現在以降status(論理値としては、この行データが利用可能かどうかの「状態」とする)が有効(=1)──の条件に合致する行データを、CSV形式で出力してみる。
まずは、条件に合致するデータを検索するクエリを作成する(この辺はMySQLもPostgreSQLも同じ)。> SELECT COUNT(*) FROM post_master; count ------- 1000 (1 row) > SELECT COUNT(*) FROM post_master WHERE limit_date >= CURRENT_DATE AND status = 1; count ------- 121 (1 row)ともに条件に該当する行データは121件であった。つまり、データ抽出用のSELECTクエリは下記のようになる。
SELECT * FROM post_master WHERE limit_date >= CURRENT_DATE AND status = 1このSELECT文を使ってCSV出力を行ってみる。
まずMySQLでは、
mysqlSELECT * FROM post_master WHERE limit_date >= CURRENT_DATE AND status = 1 INTO OUTFILE 'out_my_pick.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '"' ESCAPED BY '"' LINES TERMINATED BY '\n';次にPostgreSQLでは、
psqlCOPY (SELECT * FROM post_master WHERE limit_date >= CURRENT_DATE AND status = 1) TO 'out_pg_pick.csv' WITH CSV DELIMITER ',' FORCE QUOTE *;実際に出力されたCSVファイルはどちらも下記のようになる。
$ cat out_my_pick.csv | wc -l 121 $ cat out_pg_pick.csv | wc -l 121 $ diff out_my_pick.csv out_pg_pick.csv $ head -5 out_pg_pick.csv "33","webpage","10","Perspiciatis similique debitis et ut autem tempore dolorum.","うきいわねえさんにも...(以下省略)","2019-11-29","2024-09-01","1" "36","other","7","Ipsam error aut illum consectetur quaerat enim aut.","はしずかにはいったと思うわぎが来ま...(以下省略)","2018-04-01","2020-05-01","1" "42","other","2","Provident sit ipsum perferendis quos vel.","い、その火が燃もえて寄よったまをつるつるして、も...(以下省略)","2018-09-23","2022-10-02","1" "46","other","1","Voluptates qui et at reprehenderit.","ひじょジョバンニ、おいおうの方へ行きました。するだけないと...(以下省略)","2017-10-17","2020-11-27","1" "49","webpage","5","Assumenda ducimus aut consequuntur consequatur ea cumque.","とでもわざとうもろこびにな...(以下省略)","2017-04-16","2021-11-02","1"ともに同一のCSVが出力されていることがわかる(ともに121行のCSVファイルで、
diff取っても差分がない。最後のhead -5は、先頭5行を表示してCSVファイルのフォーマットを確認した)。
蛇足だが、この後、nkfなどで文字コードをシフトJIS+改行コードをCRLFに変換してあげれば、MSエクセルで開けるCSVファイルにすることも可能だ。とりあえず、これで基本はOKだ。
データ更新前に切り戻すためのリカバリーファイルを作成する
データベースでUPDATE文を実行して「一括データ更新が完了しましたよ〜」って報告した後に、「ごめん、更新する条件が間違ってたよ」とか「あ、更新する前にやらなきゃいけないこと忘れてた」などの予期せぬ事態が発生して、データを更新前に切り戻さなければならないケースがしばしばある(苦笑)。そういう場合に困らないように、データ更新前の状態に切り戻すためのリカバリー手段を常備しておくことが大事だ。
ところが、前項で紹介したようなデータ更新前の行データをCSV等のリストデータでバックアップしただけだと、リカバリー時にはCSVをパースして再度SQLを生成するような「切り戻し用の処理」が必要になる場合もあり、切り戻すのに時間がかかってしまう。まぁ、テーブル全体のデータを出力しておけば、MySQLなら
LOAD DATA LOCAL INFILE、PostgreSQLならCOPY FROMを使うことでCSVからでも一括でデータを切り戻すことも可能なのだが、前項の例で云えば、1000件中121件発行すれば済むクエリを1000件分すべて発行しなければならず、コストパフォーマンスが悪い。1000件程度ならば大したことがないが、これが実サービスの商用DBとかだとデータ数が数千〜数万とかの場合もざらにあるので、できる限りクエリコストは抑えたいところだ。さらに言えば、一括データ更新から切り戻し更新を行うまでの間に更新されたデータ等があった場合、それらのデータもすべからく一括データ更新前の状態に戻ってしまうので、切り戻し後にデータ不整合が発生するリスクもある。
そこで、更新が必要な最小単位のデータのみで即時切り戻しができるように、リカバリーファイルはSQLクエリの形式で出力しておくのがベストである。そのために、データ更新前の外部ファイル出力時にひと工夫しておくのだ。
例えば、前章で作成した
post_masterテーブルのデータについて、下記のような依頼が発生した場合を想定してみる。
limit_date(論理名「有効期限」)の日付が現在日より前である(=有効期限切れ)status(論理名「状態」)が有効(=1)である- 上記 1. と 2. にマッチするデータの
post_type(論理名「投稿種別」)をknowledgeに変更する該当する行データを調べてみると、
> SELECT COUNT(*) FROM post_master WHERE limit_date < CURRENT_DATE AND status = 1 AND post_type != 'knowledge'; count ------- 396 (1 row)更新対象のデータ数は396件だ。
前章のように愚直に全行データをバックアップするよりも、対象となる行データだけを更新する方がクエリを実行するコストとしては、396/1000分、約60%もお得である。
さらに、1件あたりの行データにおいては、更新が必要なのはpost_typeカラムだけなので、その1カラムのみをリストアできるようにしておけば、例え切り戻しまでに他のカラムに更新が入ったとしても、全カラムの値が切り戻されないので、切り戻しによるデータ不整合リスクはかなり軽減されることになる。それでは早速、更新対象を絞り込んだリカバリークエリファイルを作成してみよう。
MySQLでは、
SELECT CONCAT( 'UPDATE post_master SET post_type = \'', post_type, '\' WHERE post_id = ', post_id, ';' ) FROM post_master WHERE limit_date < CURRENT_DATE AND status = 1 AND post_type != 'knowledge' INTO OUTFILE 'restore_my.sql' LINES TERMINATED BY '\n';PostgreSQLでは、
COPY ( SELECT 'UPDATE post_master SET post_type = ''' || post_type || ''' WHERE post_id = ' || post_id || ';' FROM post_mster WHERE limit_date < CURRENT_DATE AND status = 1 AND post_type != 'knowledge' ) TO 'restore_pg.sql';──を実行すると、更新対象である
post_idを持つpost_typeカラムの値に現在の値を設定するUPDATE文をSELECT結果として取得し、その一覧を外部ファイルとして出力してくれる。
出力されたリカバリーファイルの中身は下記のようなSQLクエリリストだ。$ cat restore_my.sql | wc -l 396 $ cat restore_pg.sql | wc -l 396 $ diff restore_my.sql restore_pg.sql $ head -5 restore_pg.sql UPDATE post_master SET post_type = 'article' WHERE post_id = 1; UPDATE post_master SET post_type = 'article' WHERE post_id = 2; UPDATE post_master SET post_type = 'webpage' WHERE post_id = 6; UPDATE post_master SET post_type = 'other' WHERE post_id = 8; UPDATE post_master SET post_type = 'other' WHERE post_id = 9;もしこの後、切り戻しが必要になった場合、このリカバリー用のSQLクエリリストを実行するだけで、最小コストと低リスクで元データに復旧できるという次第だ。
では、お題である依頼の対応を行ってみる。
> UPDATE post_master SET post_type = 'knowledge' WHERE limit_date < CURRENT_DATE AND status = 1 AND post_type != 'knowledge'; UPDATE 396 > SELECT COUNT(*) FROM post_master WHERE limit_date < CURRENT_DATE AND status = 1 AND post_type != 'knowledge'; count ------- 0 (1 row)これで依頼の対応は完了である。「対応完了しました〜」と依頼者に報告しても構わないだろう。
なお、上記の実行例はPostgreSQLのものだが、MySQLでも同じクエリで実行可能だ。次に、データの切り戻しも行ってみる。
DBコンソールを抜けて、コマンドラインに戻り、リカバリーファイルがあるディレクトリに移動した想定で進める。MySQLでは、
$ mysql -u DBユーザ名 -p DB名 < restore_my.sqlPostgreSQLでは、
$ psql -U DBユーザ名 -f restore_pg.sql DB名これで切り戻し完了だ。
データを確認してみよう。> SELECT COUNT(*) FROM post_master WHERE limit_date < CURRENT_DATE AND status = 1 AND post_type != 'knowledge'; count ------- 396 (1 row)元に戻っている。
まとめ・注意点
このデータ運用手法は私が実際のサービス保守作業で使っているので、けっこう実践的だと思う。
しかし、注意しないといけない点もいくつかある。
- リカバリーファイル用のUPDATE文をSELECTクエリ内で成形する際、適切なエスケープを行う必要がある。 SQL上ではプレースホルダ型の値埋め込みや禁則文字のエスケープができないため、それぞれのDBMSに準じたエスケープ表記を自前で施してあげる必要がある。これを失敗すると、ぶっ壊れたUPDATE文が生成されて、最悪、リカバリー時にテーブルのデータが壊滅的な状況に陥る危険性を孕んでいる。例えば改行が含まれるようなフリーテキストのデータを更新対象にする場合などは、改行コードやクォート文字をSQL上で自前でエスケープするのには限界がある。そういうデータを取り扱う際にはこの方法はお勧めしない。シェル化したり、スクリプト言語で個別処理を組んだ方が良いだろう。
- 更新頻度の高いカラムの値を更新するのには向かない。 データ更新後に切り戻しが発生した場合、リカバリー時までに切り戻し対象のカラムに別の更新が発生してしまっていると、リカバリーでその更新が失われてしまう可能性がある。そもそもそういうカラムの値を更新するのであれば、サービスをメンテナンス状態にしたり、一時的にテーブルをロックするなりして、データ更新後の確認が完了するまでは他からの更新を抑止しておく等の措置が必要になってくる。
- リカバリー用のUPDATE文が複雑になる場合はこの方法は避けた方が良い。 リカバリー用のUPDATE文の条件が煩雑だったり、サブクエリを介したり、複数のUPDATE文が必要だったりというようなケースでもこの運用方法は対応できるが、リカバリー時のリスクが大きくなることを認識しておく必要がある。そういうケースは対象テーブルの全データをCSV等に出力しておき、リカバリーも一括で全データを切り戻すという運用の方が無難だったりする(リカバリー時の発行クエリのコストにもよるが……)。
上記のような注意点を踏まえ、最低限テスト環境等でリカバリーのテストをした上で使えば、たった2つのSQLだけで「心穏やかに」データ更新が行えるはずだ。
まぁ、極力、商用データベース上でこういう胃の痛くなるような作業が発生しないことを願ってやまない──というのが本音であるw
- 投稿日:2020-01-14T10:20:36+09:00
【Laravel】セルを連結して検索
Laravel6.0
登録したユーザーの氏名(family_name)と名前(last_name)を連結してLike検索をしたいときなど。
時々、クエリの中でSQLを直接使用したいことがあります。エスケープなしのSQLを使用する場合はDB::rawメソッドを使用します。
DB::rawメソッドはエスケープ処理がされていないのでSQLインジェクションに注意。
CONCAT関数を使用する。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.5 文字列関数User::where(DB::raw('CONCAT(family_name, last_name)'), 'like', '%'.$text.'%')->get();参考
- 投稿日:2020-01-14T09:00:37+09:00
【MySQL】データベースにユーザーを作成・権限付与する
MySQLのDBでユーザーを作成・権限付与などするときのコマンド覚書です。
SQLgrant all privileges on db_name.* to db_user@'localhost' identified by 'db_pass' with grant option;これは、以下の意味となります。
SQLgrant [権限] on [適用対象のデータベース].[適用対象のテーブル] to 'ユーザ名'@'ホスト名' identified by 'パスワード';
with grant optionを指定すると他のユーザーにも同等の権限を与えられるようになる。grant all privilegesのprivilegesは省略可能でgrant all on db_name~でもよい。- 対象ユーザーが未作成の場合は
create文を使わなくてもgrant文で作成可能。参考URL
MySQLでユーザを作成し、権限を設定する方法
ユーザーを作成する(CREATE USER文)
GRANT -- アクセス権限の定義
IBM Knowledge Center | WITH GRANT OPTION キーワード
- 投稿日:2020-01-14T08:19:28+09:00
Boost.Asioで名前解決(非同期)
使用したツールなど
前の記事を参照
やりたかったこと
- 「非同期」にする。
- 「POST形式」で送信する。
- 「クラス」にする。
コード
クライアント:
main.cpp#include <iostream> #include <vector> #include <iomanip> #include <boost/asio.hpp> #include <boost/bind.hpp> #include <boost/algorithm/string.hpp> #include "json11.hpp" using namespace std; namespace asio = boost::asio; using asio::ip::tcp; class Client { asio::io_service& io_service_; tcp::socket socket_; tcp::resolver resolver_; asio::streambuf sendbuf_; asio::streambuf recvbuf_; int value1, value2; const string post = "addapp.php"; const string host = "127.0.0.1"; public: Client(asio::io_service& io_service, int v1, int v2) : io_service_(io_service), socket_(io_service), resolver_(io_service), value1(v1), value2(v2) { } void connect() { tcp::resolver::query query(host, "http"); resolver_.async_resolve( query, boost::bind(&Client::on_resolve, this, asio::placeholders::error, asio::placeholders::iterator)); } private: void on_resolve(const boost::system::error_code& error, tcp::resolver::iterator endpoint_iterator) { /*if (error) { cout << "resolve failed: " << error.message() << endl; return; }*/ asio::async_connect( socket_, endpoint_iterator, boost::bind(&Client::on_connect, this, asio::placeholders::error)); } void on_connect(const boost::system::error_code& error) { /*if (error) { cout << "connect error : " << error.message() << endl; } else { cout << "connect!" << endl; }*/ //リクエストを送信 async_send(); //レスポンスを受信 async_recv(); } void async_send() {; ostream request_stream(&sendbuf_); string query = "value1=" + to_string(value1) + "&value2=" + to_string(value2); request_stream << "POST " << post << " HTTP/1.1\r\n"; request_stream << "Host: " << host << "\r\n"; request_stream << "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n"; request_stream << "Content-Length: " << query.length() << "\r\n"; request_stream << "Connection: Close\r\n"; request_stream << "\r\n"; request_stream << query << "\r\n"; asio::async_write(socket_, sendbuf_.data(), boost::bind(&Client::send_end, this, asio::placeholders::error)); } void send_end(const boost::system::error_code& error) { //cout << "request : " << asio::buffer_cast<const char*>(sendbuf_.data()) << endl; sendbuf_.consume(sendbuf_.size()); } void async_recv() { asio::async_read(socket_, recvbuf_, asio::transfer_all(), boost::bind(&Client::recv_end, this, asio::placeholders::error)); } void recv_end(const boost::system::error_code& error) { //cout << "response : " << asio::buffer_cast<const char*>(recvbuf_.data()) << endl; string_to_json(asio::buffer_cast<const char*>(recvbuf_.data())); recvbuf_.consume(recvbuf_.size()); } void string_to_json(string str) { vector<string> res; res = boost::split(res, str, boost::is_any_of("\r\n\r\n")); string err; const json11::Json jsonData = json11::Json::parse(res[res.size() - 1], err); auto jsonArray = jsonData.array_items(); cout << " id value1 value2 ans" << endl; for (auto &item : jsonArray) { cout << setw(6) << right << item["id"].string_value() << " "; cout << setw(6) << right << item["value1"].string_value() << " "; cout << setw(6) << right << item["value2"].string_value() << " "; cout << setw(6) << right << item["ans"].string_value() << '\n'; } } }; int main() { asio::io_service io_service; int value1, value2; cin >> value1 >> value2; Client client(io_service, value1, value2); client.connect(); io_service.run(); }サーバー:
add.php<?php //計算する値を入力 $isInput = TRUE; if(isset($_POST['value1'])){ $value1 = intval($_POST['value1']); } else { $isInput = FALSE; } if(isset($_POST['value2'])){ $value2 = intval($_POST['value2']); } else { $isInput = FALSE; } //DBの設定 $mysql = mysqli_connect('localhost', 'root', '') or die(mysqli_error($mysql)); mysqli_select_db($mysql, 'calc_db'); mysqli_query($mysql, 'SET NAMES UTF8'); //入力があるならデータをDBに追加 if($isInput){ //結果を計算 $ans = $value1 + $value2; //DBにデータを挿入 $insert = sprintf('INSERT INTO result SET value1=%d,value2=%d,ans=%d', mysqli_real_escape_string($mysql, $value1), mysqli_real_escape_string($mysql, $value2), mysqli_real_escape_string($mysql, $ans) ); mysqli_query($mysql, $insert) or die(mysqli_error($mysql)); } //DBからデータを降順で取得 $request = sprintf('SELECT * FROM result'); $result = mysqli_query($mysql, $request) or die(mysqli_error($mysql)); $ansList = array(); while($row = mysqli_fetch_assoc($result)){ $ansList[] = array( 'id'=>$row['id'], 'value1'=>$row['value1'], 'value2'=>$row['value2'], 'ans'=>$row['ans'] ); } //jsonで出力 $jsonData = json_encode($ansList); header("Content-Type: application/json; charset=utf-8"); echo $jsonData; ?>変更点など
出力結果は前回と同じである。
main.cppは、目的である「非同期」「POST形式」「クラス」を満たすようにした。
エラー発生時のコメントは表示しない。app.phpは、「\$_GET」の部分を「\$_POST」にしただけである。
まとめ
この書き方が正しいかは分からないが取り敢えず実行はできた。
これを使って簡単なゲームでも作りたい。参考にしたサイト
- 投稿日:2020-01-14T01:12:41+09:00
Docker で楽々複数MySQL立ち上げ
はじめに
この記事はDockerのMySQLの公式イメージを使って複数のMySQLを立てるのがゴールです
参考記事
dockerでmysqlを使う利用するもの
- Docker for Mac
DockerをMacにインストールする(更新: 2019/7/13)
- シェルスクリプト
作成したシェルスクリプト
Docker for Macを入れたあと
公式のイメージを利用して
- mysql5.6
- mysql5.7
- mysql8.0
この三つをpullします
pullした後にそれらを起動します
mysql.shdocker pull mysql:5.6 && docker pull mysql:5.7 && docker pull mysql:8.0 docker run --name mysql5.6 -p 3356:3306 -e MYSQL_USER=root -e MYSQL_ROOT_PASSWORD=pass -d mysql:5.6 docker run --name mysql5.7 -p 3357:3306 -e MYSQL_USER=root -e MYSQL_ROOT_PASSWORD=pass -d mysql:5.7 docker run --name mysql8.0 -p 3380:3306 -e MYSQL_USER=root -e MYSQL_ROOT_PASSWORD=pass -d mysql:8.0作成したシェルスクリプトを実行
MySQLに接続してみる
$ mysql -uroot -h127.0.0.1 -p -P3357-Pの部分を
5.6を使いたかったら3356
5.7を使いたかったら3357
8.0を使いたかったら3380
にすれば使い分けることができますdocker run --name mysql5.6 -p 3356:3306 -e MYSQL_USER=root -e MYSQL_ROOT_PASSWORD=pass -d mysql:5.6-pの内容を変えればポート番号を変えれますし
MYSQL_USER=root
MYSQL_ROOT_PASSWORD=passここをいじればユーザー名とパスワードを変えれます