20220116のMySQLに関する記事は6件です。

【Rails × AWS】本番環境のDB(MySQL)をRDSに移行し、タイムゾーンを日本時間に変更する

EC2環境にデプロイしていたRailsアプリのデータベースを後からRDSに移行したので自分自身の復習も兼ねて手順をまとめました。 前提 Rails -v 6.0.4 MySQL -v 5.7.36 サーバー: nginx AWS側の設定 AWSのRDSダッシュボードを開く 「データベースの作成」を選択 エンジンのタイプを選択 現在使用しているSQLの種類を選択。 今回はMySQLを選択します。 4.MySQlのバージョンを選択 現在使用しているMySQLのバージョンと同じものを選択します。 MySQLのバージョンの確認方法は下記の通りです。 [ec2-user]$ mysql -u root -p mysql> select version(); 5.テンプレートを選択 今回は無料枠を利用することとします。 下の方に概算コストが表示されるので高性能なテンプレートを選択する場合はそちらを確認してください。 6.基本情報を設定 いずれの項目も自由に設定できますがマスターユーザー名とパスワードは後で使用するので控えるようにしてください。 7.DBインスタンスのクラスを選択 db.t2.microを選択します。1GBのメモリを搭載、実験に適したサイズです。 8.ストレージを設定 今回は初期設定のままとします。 1年間は無料利用枠の対象となります。 9.可用性と耐久性 無料利用枠のテンプレートを選択した場合こちらは設定できないのでスルーします。 10.接続の設定 VPCとサブネットグループに関しては現在使用しているEC2インスタンスと同じ場所に作成します。 パブリックアクセスはこのDBインスタンスにパブリックIPを割り当てるかどうかを設定するものになります。同じVPCに設置したEC2インスタンスから接続できれば十分なので「いいえ」を選択します。「はい」にすると攻撃されるリスクがあります。 VPCセキュリティグループは新規で作成します。あとで中身は設定します。 アベイラビリティゾーンはEC2インスタンスと同じ場所に配置するので自身のEC2インスタンスのアベイラビリティゾーンを確認して選択してください。 11.データベースの作成を選択する 残りの項目については今回はデフォルトのままでいきます。 12.データベースの完成を待つ DBインスタンス一覧から先ほど作成したDBインスタンスのステータスを確認し、「利用可能」となっていれば作成は完了です。 13.セキュリティグループの設定 DBインスタンスをクリックし、「接続とセキュリティ」からセキュリティグループを選択。「インバウンドルールの設定を編集」を選択。 デフォルトの設定を削除し、下記のように設定します。 ソースには使用しているEC2インスタンスに設定したセキュリティグループIDを指定します。 ここまで設定したら「ルールを保存」してください。 14.エンドポイントを確認する 再度、RDSダッシュボードからDBインスタンスを選択し、エンドポイント欄の数値をどこかにコピーしておいてください。 EC2上のMySQLからDBインスタンスにデータを移行する ここからはターミナル操作でデータを移行する手順です。 15.EC2インスタンス上でバックアップを取る [ec2-user]$ mysqldump --databases railsdb -u root -p > /tmp/railsdb.sql 'railsdb' の箇所は現在MySQL上で使用している移行したいデータベース名に置き換えてください。 ここで要求されるパスワードはrootユーザーのパスワードを入力してください。 16.RDSのDBインスタンスにリストアする mysql -h [エンドポイント] -u admin -p < /tmp/railsdb.sql エンドポイントには先ほど確認したエンドポイントを入力し、'admin'のところには先ほど設定したRDSのユーザー名を入力してください。ここで求められるパスワードもRDS用のマスターパスワードを入力します。 Rails側の設定 ここからはRailsのdatabase.ymlの設定を変更します。 17.database.ymlを書き換える #変更前のdatabase.yml production: <<: *default database: <%= Rails.application.credentials.db[:database] %> username: <%= Rails.application.credentials.db[:username] %> password: <%= Rails.application.credentials.db[:password] %> socket: <%= Rails.application.credentials.db[:socket] %> 環境変数としてそれぞれ設定してありますがRDS用のuser名とpasswordに変更していきます。さらにhostの項目を追加します。 #localターミナル $ EDITOR=vim bin/rails credentials:edit credentials.yml db: database: 変更しない username: admin(RDS用のユーザー名) password: RDS用の設定したPW socket: 変更しない host: エンドポイント #変更後のdatabase.yml production: <<: *default database: <%= Rails.application.credentials.db[:database] %> username: <%= Rails.application.credentials.db[:username] %> password: <%= Rails.application.credentials.db[:password] %> socket: <%= Rails.application.credentials.db[:socket] %> host: <%= Rails.application.credentials.db[:host] %> これで移行自体は完了です。 RDSのtime-zone設定 上記の時点でRDSへ移行は完了しましたがRDSはデフォルトでtime-zoneがUTCで設定されています。 [ec2-user]$ mysql -h [エンドポイント] -P 3306 -u [RDSのユーザー名] -p mysql> show variables like '%time_zone%'; +------------------+------------+ | Variable_name | Value | +------------------+------------+ | system_time_zone | UTC | | time_zone | UTC | +------------------+------------+ mysql> SELECT NOW(); UTC時間が表示される system_time_zoneはいじれないようなのでtime_zoneの方を日本時間に設定する手順を紹介します。 1.AWSのRDSページにアクセスする 2.パラメータグループを選択する 3.パラメータグループの作成を選択 4.time_zoneの項目にAsia/Tokyoを選択し、保存する 5.ダッシュボードから変更を選択 6.DBパラメータグループに先ほど作成したグループを選択。 変更を保存する。 7.「アクション」から再起動を選択 再起動後、DBインスタンスの設定項目のパラメータグループが「同期中」となっていることを確認する。 [ec2-user]$ mysql -h [エンドポイント] -P 3306 -u [RDSのユーザー名] -p mysql> show variables like '%time_zone%'; +------------------+------------+ | Variable_name | Value | +------------------+------------+ | system_time_zone | UTC | | time_zone | Asia/Tokyo | +------------------+------------+ mysql> SELECT NOW(); 日本時間が表示される 上記のようになっていればOK.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

分離レベルが異なるトランザクション間での独立性について実験(MySQL/InnoDB)

DBのトランザクション分離レベルを勉強していた際、これは「他のトランザクションの影響を受けない」のか「他のトランザクションに影響を与えない」のか(あるいはその両方なのか)が気になりました。 結果を推測できるほどの知識はまだ全然ないため、ひとまず実際に MySQL (InnoDB) で試して確認しました。 ※他のDBだと異なる結果になるかもしれません。 TL;DR 「他のトランザクションの影響を受けない」だけのようです。 他のトランザクションの分離レベルが低くても、自分の処理内容は自分の分離レベルに応じて安全が守られる(たぶん) 自分の読み込みのためにスナップショットを作ることがある 必要に応じて排他制御が働き、相手か自分自身が処理を待機する 自分の処理内容が他のトランザクションから見られてしまうことがある 実際に SERIALIZABLE でのコミット前の内容が READ UNCOMMITTED から見えた 実験準備 サンプルデータ MySQL のリファレンスマニュアルから適当に持ってきました。 samples.sql -- https://dev.mysql.com/doc/refman/8.0/ja/innodb-transaction-isolation-levels.html CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB; INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2); COMMIT; サーバとクライアント MySQLサーバを用意し、2つのクライアントから接続してクエリを投入できるようにします。 簡単に試せるといいので、今回はDockerでコンテナをひとつ立ち上げてその中で完結させます。 # サーバを起動 docker run --rm -d --name mysql-test-server \ -e MYSQL_ROOT_PASSWORD= \ -e MYSQL_ALLOW_EMPTY_PASSWORD=yes \ -e MYSQL_DATABASE=tx_test_db \ mysql:8.0.27 # (起動完了するのを確認) docker logs -f mysql-test-server # サンプルデータを投入 docker exec -i mysql-test-server mysql tx_test_db < samples.sql # 同じコンテナ内でクライアントを実行して接続(別のターミナルからが良い) docker exec -it mysql-test-server mysql tx_test_db # --> 自由にクエリを投入して実験する # サーバを停止し、コンテナを破棄 docker stop mysql-test-server 実験 以下では connection A と connection B から並行してクエリを投入します。 同じ分離レベル(確認) READ COMMITTED 同士 まずはよくあるパターンを試しておきます。 READ COMMITTED は名前通り、コミットされたデータを読みます。言い換えるとコミット前のデータは読むことがありません。 これは非再現リード(non-repeatable read)という現象が起こります。行儀よくひとりずつトランザクションを実行した際は、トランザクション内で連続して SELECT すると常に同じ結果が返ります。しかし他の人が並行作業していると途中でデータが書き換わり、連続して SELECT してもタイミング次第で異なる結果となり得ます。 -- connection A / connection B -- SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#1) BEGIN; UPDATE t SET b = 2 WHERE b != 2; --> 2 rows affected --> Rows matched: 2 Changed: 2 Warnings: 0 BEGIN; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#2) COMMIT; SELECT * FROM t WHERE b = 2; --> 5 rows in set (#3) !! non-repeatable read !! COMMIT; A側はトランザクション内で2回 SELECT しました。B側がトランザクション内で UPDATE しただけではA側には影響が出ませんでしたが( #2 )、B側がコミットするとA側に影響が出ました( #3 )。 REPEATABLE READ 同士 前節の現象は REPEATABLE READ では起きないことも確認しておきます。 -- connection A / connection B -- SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#1) BEGIN; UPDATE t SET b = 2 WHERE b != 2; --> 2 rows affected --> Rows matched: 2 Changed: 2 Warnings: 0 BEGIN; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#2) COMMIT; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#3) COMMIT; SELECT * FROM t WHERE b = 2; --> 5 rows in set (#4) B側でコミットした後の #3 でも #2 と同じ結果が返りました。トランザクションを終えた後の #4 では、B側のコミットが反映されています。 同じ結果が返る仕組みは、トランザクションの最初の #2 でスナップショットを作ってそこから読み取っているからだそうです。(一方で READ COMMITTED では毎回スナップショットを作り直していて、 #2 と #3 とで異なっています) 異なる分離レベル SERIALIZABLE と READ UNCOMMITTED (1) これらは分離レベルの最も高いものと最も低いものです。 SERIALIZABLE なトランザクションの中の作業は恐らく外部の影響を受けないでしょうが、逆に中の作業が外部に漏れる(影響を与える)ことがあるのかが気になります。 これまでと同じものをやってみます。本節はA側( SELECT )が SERIALIZABLE 、B側( UPDATE )が READ UNCOMMITTED です。 -- connection A / connection B -- SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#1) BEGIN; UPDATE t SET b = 2 WHERE b != 2; --> 2 rows affected --> Rows matched: 2 Changed: 2 Warnings: 0 BEGIN; SELECT * FROM t WHERE b = 2; --> (wait) (#2) COMMIT; --> 5 rows in set (#2') COMMIT; A側は SELECT が待機状態になり、B側のトランザクションが完了してから UPDATE 反映後の結果が返りました。 【理屈】 SERIALIZABLE で普通の SELECT を実行すると、 SELECT ... FOR SHARE と扱われ共有ロックを取得します。しかし #2 のタイミングではB側の UPDATE が排他ロックを取得していて競合するため、排他ロックがなくなるまで待つことになります。 SERIALIZABLE と READ UNCOMMITTED (2) (1)では UPDATE が先だったため SELECT が待たされました。逆順も試してみます。 -- connection A / connection B -- SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; BEGIN; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#1) BEGIN; UPDATE t SET b = 2 WHERE b != 2; --> (wait) COMMIT; --> 2 rows affected --> Rows matched: 2 Changed: 2 Warnings: 0 COMMIT; 今度はB側の UPDATE が待機状態になり、A側のトランザクションが終了してから処理されました。 SERIALIZABLE と READ UNCOMMITTED (3) (1)と分離レベルを入れ替えてみます。A側( SELECT )が READ UNCOMMITTED 、B側( UPDATE )が SERIALIZABLE です。 ※ついでに今回だけB側はトランザクションをロールバックします。 -- connection A / connection B -- SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#1) BEGIN; UPDATE t SET b = 2 WHERE b != 2; --> 2 rows affected --> Rows matched: 2 Changed: 2 Warnings: 0 BEGIN; SELECT * FROM t WHERE b = 2; --> 5 rows in set (#2) !! dirty read !! ROLLBACK; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#3) COMMIT; READ UNCOMMITTED は #2 のタイミングで、 SERIALIZABLE がコミットしていないデータを読み取れました(ダーティリード)。 SERIALIZABLE の中は外部から隠蔽される、というわけではないようです。 SERIALIZABLE と READ COMMITTED 念のため(3)からA側の分離レベルを READ COMMITTED に上げた場合も試してみます。 -- connection A / connection B -- SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#1) BEGIN; UPDATE t SET b = 2 WHERE b != 2; --> 2 rows affected --> Rows matched: 2 Changed: 2 Warnings: 0 BEGIN; SELECT * FROM t WHERE b = 2; --> 3 rows in set (#2) COMMIT; SELECT * FROM t WHERE b = 2; --> 5 rows in set (#3) !! non-repeatable read !! COMMIT; SEREALIZABLE のコミット前のデータが読まれることはなく、最初の「 READ COMMITTED 同士」と同じようになりました。 まとめ 自分へ適切な分離レベルを設定した際、「他のトランザクションの影響を受けない」のは正しそうですが、「他のトランザクションに影響を与えない」というわけではありませんでした。影響を与えるかどうかは、相手の分離レベルが他から影響を受けるかどうか次第であり、自分が完全に制御できるわけではなさそうです。 別の見方をすれば、 READ UNCOMMITTED に設定すれば他のトランザクション内の様子を覗き放題ということでしょうか。 参考 MySQL 8.0 リファレンスマニュアル トランザクション分離レベル 一貫性非ロック読み取り InnoDB ロック DockerHub mysql (Official Image)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WSL2(Ubuntu)でPHP + MySQL環境を構築する①

WSL2(Ubuntu)を使用して、Windows上にLinuxの仮想環境を構築し、PHP + MySQLの開発環境を整えます。 本稿は2部構成となっております。第1部では、WSL2・Ubuntuをインストールして、Linux環境を構築するところまで進めます。 前提条件 Window10環境 当記事は2022年1月時点の情報に基づいて、執筆しています。WSL関連の情報は頻繁にアップデートされているようですので、実行前に公式ドキュメントを確認することをお勧めします。 公式ドキュメント : https://docs.microsoft.com/ja-jp/windows/wsl/install Windowsのバージョン確認 作業に入る前に、まずはWindowsのバージョンを確認しておきましょう。 Windowsキー + Rでファイル名を指定して実行画面を開きます。名前欄にwinverと入力してOKをクリックすると、Windowsのバージョン情報が確認できます。 ここでバージョンのビルドが 19041 以降であることを確認しておきましょう。19041 に満たない場合は、Windowsアップデートを行ってください。 WSL2のインストール スタートメニューからpowershellと検索し、Windows PowerShellを管理者として実行し、次のコマンドを入力します。 64ビット版のWindowsをお使いの方はpowershellで検索すると、無印(64ビット版)とx86(32ビット版)が出てきますが、無印のほうを使用してください。 PowerShell wsl --install インストールが正常に終了すると、PCを再起動するように促すメッセージが表示されますので、言われたとおりに再起動します。 Ubuntuのインストールと初期設定 Ubuntuのインストール Windowsを再起動すると、自動的にUbuntuがインストールされます。下記のような画面が表示されますので、インストール完了までしばらく待ちます。 ユーザー名とパスワードの設定 インストールが終わると、下記のようにユーザー名とパスワードを設定するよう求められます。任意のユーザー名とパスワードを入力します。 Ubuntu Enter new UNIX username: # 任意のユーザー名を入力 New password: # 任意のパスワードを入力 Retype new password: # パスワードを再度入力 パスワードを入力する際、セキュリティ上の観点からキーボードにタイプしても画面上には反映されませんが、きちんと入力できています。 最新版にアップデートする 下記コマンドを入力して、Ubuntuを最新版にアップデートしておきましょう。 Ubuntu sudo apt update && sudo apt upgrade 日本語環境設定をする 必須ではありませんが、日本語環境設定をしておくと扱いやすいのでやっておきます。 まずは、タイムゾーン設定です。 Ubuntu sudo dpkg-reconfigure tzdata 実行すると下記のような画面が表示されるので、Asiaを選択してOk(Enterキー) ⇒ Tokyoを選択してOk(Enterキー)を入力します 次に言語設定です。 Ubuntu sudo apt install locales sudo apt install -y language-pack-ja sudo update-locale LANG=ja_JP.UTF-8 最後に、man(マニュアルのようなもの)の日本語版を入れておきましょう。 Ubuntu sudo apt install -y man manpages-ja manpages-ja-dev ここまで完了したら、一旦Ubuntuの画面を終了し、スタートメニューからUbuntuを検索して再度立ち上げましょう。日本語の設定が反映されます。 動作の確認 きちんとWSL2でUbuntuを動かせているか確認しておきます。 もう一度Windows PowerShellを開いて、下記コマンドを実行します。 PowerShell wsl -l -v 下記のような表示が返ってきたら成功です。きちんと動作しています。 PowerShell NAME STATE VERSION * Ubuntu Running 2 WindowsのGUIでUbuntuを操作する CUIの操作に慣れてないうちは、UbuntuをGUIで操作したり確認したい場合もあるでしょう。WindowsのGUI画面からUbuntuの操作をする方法を解説します。 Windowsキー + Eでエクスプローラを開き、アドレスバーに\\wsl$\Ubuntu\home\{Ubuntuのユーザー名}と入力して、Enterを押します。そうすると、Ubuntuのホームディレクトリの中身をGUIで確認できます。ここからLinuxコマンドを使用せずに、ファイルを作成したり削除することもできます。 デスクトップにショートカットを作成しておくとよいでしょう。 まとめ ここまでの作業お疲れさまでした。第2部では、VScode、PHP、MySQL、Appacheの導入をしていきたいと思います。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

{Rails}uninitialized constant * (NameError)と出た場合の対処法

①やりたいこと rails cでレコードを確認&作成したいが、uninitialized constant User (NameError)と出てしまう。※Userはモデル名です。 ②エラー内容 % rails c Running via Spring preloader in process 18643 Loading development environment (Rails 6.1.4.4) irb(main):001:0> User.all (irb):1:in `<main>': uninitialized constant User (NameError) ③原因 uninitialized constant * (NameError)は、Rubyで「定数やclassが定義されていない」ことを意味しています。 ファイル名の記述間違いなどで、呼びたいクラス名を記述できていない場合などで発生します。 この場合はコントローラー名が間違っていたみたいです。 ④解決策 ターミナルを遡ってみると、コントローラーを作成しているところまで来たのですが、、 usersじゃなくて正しくはUsersでした。 % rails g controller users Running via Spring preloader in process 14608 create app/controllers/users_controller.rb invoke erb create app/views/users invoke test_unit create test/controllers/users_controller_test.rb invoke helper create app/helpers/users_helper.rb invoke test_unit invoke assets invoke scss create app/assets/stylesheets/users.scss なので、間違えて作成したコントローラーを消して、新しくコントローラーを作成し直す必要があります。 調べてみたら、% rails destroy controllerで消すことができるとのこと。 % rails destroy controller users Running via Spring preloader in process 19108 remove app/controllers/users_controller.rb invoke erb remove app/views/users invoke test_unit remove test/controllers/users_controller_test.rb invoke helper remove app/helpers/users_helper.rb invoke test_unit invoke assets invoke scss remove app/assets/stylesheets/users.scss が、ここで問題発生。ちゃんと消えたか確認するためにapp/controllersを開いてみると、users_controllerがまだ表示されていました。VScodeだとこの表示消えないのかな。。 ファイルの所で右クリックしてusers_controllerだけ削除してみました。 この状態で再度 % rails g controller Usersでコントローラーを作成しようとすると、作成自体はできますが下の方に The name 'UsersHelper' is either already used in your application or reserved by Ruby on Rails. と出ます。仕方ないので users_helperも右クリックして削除項目を選びます。その後% rails g controller Usersを実行して、controllerが作成されたのを確認してから、rails cコマンドを実行。その結果が下記です。 % rails c Running via Spring preloader in process 19669 Loading development environment (Rails 6.1.4.4) irb(main):001:0> User.all User Load (0.6ms) SELECT `users`.* FROM `users` => [#<User:0x00007fafca4c9538 id: 2, name: "you", email: nil, pass: nil, image: nil, created_at: Fri, 14 Jan 2022 04:40:49.121048000 UTC +00:00, updated_at: Fri, 14 Jan 2022 04:40:49.121048000 UTC +00:00>, #<User:0x00007fafce64c690 id: 3, name: "he", email: nil, pass: nil, image: nil, created_at: Fri, 14 Jan 2022 04:41:44.970457000 UTC +00:00, updated_at: Fri, 14 Jan 2022 04:41:44.970457000 UTC +00:00>] irb(main):002:0> 無事レコード一覧が出せました! あとがき:コントローラー名の誤りという見立ては間違ってないのですが、% rails destroy controllerでちゃんと削除できなかったのだけがなんとも。VScodeだと消えないんですかね〜。。 有識者の方ご意見くださると嬉しいです。 ただ目的としていたレコードの確認はできたので、結果オーライ?ではあります。 開発環境 mac OS バージョン11.6 Rails 6.1.4.4 Ruby 3.0.3 MYSQL 0.5 エディタ VScode
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】配列文字列をreplaceして検索するスコープ機能

はじめに VARCHAR型に以下の形式で登録していくシステムに検索機能を追加した際の対処方法をまとめます ["40","41","42","43","44","45","46","47"] やり方 Model内で共通利用したいのでScope化する FIND_IN_SET:第1引数の値が第2引数に含まれている場合に範囲を返す ?:ユーザーの入力値を使用するのでプリペアドステートメントでバインドする replace:カラム名,置換元文字列,置換先文字列を指定する "FIND_IN_SET(?,replace(replace(replace({$target_column},'[',''),']',''),'\"',''))", $item); User.php public function scopeWhereInByArray($query, $target_column, $ids) { $query->where(function ($subQuery) use ($ids, $target_column) { foreach ($ids as $item) { $subQuery->orWhereRaw("FIND_IN_SET(?,replace(replace(replace({$target_column},'[',''),']',''),'\"',''))", $item); } }); } Scopeをロジックで利用する UserController.php // クエリを生成 $query = User::query(); // 都道府県検索 if ($param->getPrefIds()) { $query->whereInByArray('pref', $param->getPrefIds()); } さいごに 読んでいただきありがとうございます。 いいねしていただけると記事執筆の励みになりますので、参考になったと思った方は是非よろしくお願いします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【MySQL,Docker】MySQLの文字化けを解消する方法

概要 本記事は、PHPフレームワークLaravel入門 第2版で学習している中の疑問・つまづきの備忘録です。 今回はMySQLの文字化けを解消する方法について調べたことをまとめます。 環境 Docker Laravel Sail MySQL8.0 今回起きた現象 boardsテーブルの2行目のtitleは、本来「こんにちは」と入力されていますが、「?????」と文字化けしています。 mysql> select * from boards; +----+-----------+--------------+---------+---------------------+---------------------+ | id | person_id | title | message | created_at | updated_at | +----+-----------+--------------+---------+---------------------+---------------------+ | 1 | 1 | Hello | xxxxxxx | 2022-01-11 07:32:54 | 2022-01-11 07:32:54 | | 2 | 2 | ????? | yyyyy | 2022-01-11 07:33:24 | 2022-01-11 07:33:24 | | 3 | 1 | good_morning | xxxxxx | 2022-01-11 09:32:31 | 2022-01-11 09:32:31 | +----+-----------+--------------+---------+---------------------+---------------------+ 原因 文字セットがどのように設定されているかを確認したところ以下のような状態でした。 mysql> show variables like "chara%"; +--------------------------+--------------------------------+ | Variable_name | Value | +--------------------------+--------------------------------+ | character_set_client | latin1 | | character_set_connection | latin1 | | character_set_database | utf8mb4 | | character_set_filesystem | binary | | character_set_results | latin1 | | character_set_server | utf8mb4 | | character_set_system | utf8mb3 | | character_sets_dir | /usr/share/mysql-8.0/charsets/ | +--------------------------+--------------------------------+ 8 rows in set (0.01 sec) 結果としては、 character_set_client character_set_connection character_set_results の3項目でlatin1が設定されています。 これらをutf8mb4に変更する必要があるようです。 character_set_client・・・サーバーは、character_set_client システム変数値を、クライアントが送信するステートメントの文字セットにします。 character_set_connection・・・クライアントによって送信されたステートメントを character_set_client から character_set_connection に変換します。 character_set_results・・・サーバーがクライアントにクエリー結果を返信するときに使用する文字セットを示します。 MySQL 8.0 リファレンスマニュアル 修正方法 まずはmysqlのコンテナに入り、複数ある設定ファイルmy.cnfの読み込み順序を調べます。 MySQL 8.0 リファレンスマニュアル $ docker exec -it [MySQLのコンテナ名] /bin/bash ///mysqlのコンテナに入る bash-4.4# mysql --help Default options are read from the following files in the given order: /etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf 今回コンテナ内にはデフォルトで/etc/my.cnf以外のファイルは存在しなかったので、ここを変更していきます。 /etc/my.cnf [mysqld] #コメントは省略 skip-host-cache skip-name-resolve datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock secure-file-priv=/var/lib/mysql-files user=mysql pid-file=/var/run/mysqld/mysqld.pid 今回はDockerを用いて環境構築しているので、以下の順序で修正していきます。 1. /mysql/etc/my.cnfファイルをローカルに作成 2. mysqlコンテナ内の/etc/my.cnfの内容を1.のファイルにコピー 3. ローカルの/mysql/etc/my.cnfに設定を追記 4. docker-compose.ymlを修正 1. /etc/my.cnfファイルをローカルに作成 プロジェクトルート直下にmysqlディレクトリを作成、その配下にetc/my.cnfを作成する。 2. mysqlコンテナ内の/etc/my.cnfの内容を1.のファイルにコピー 今回は、デフォルトの状態は崩さず文字化けを直したいので、先ほど確認した/etc/my.cnfの内容をローカルの/mysql/etc/my.cnfにコピーします。 3. ローカルの/mysql/etc/my.cnfに設定を追記 MySQLのリファレンスには以下のように記載があります。 ここでいうオプションファイルは、my.cnfのことです。したがって、/mysql/etc/my.cnfにdefault-character-setに関する設定を追記します。 mysql クライアントでは、デフォルトとは異なる文字セットを使用するために、サーバーに接続するたびに SET NAMES ステートメントを明示的に実行できます (クライアントプログラム接続文字セット構成 を参照)。 同じ結果をより簡単に得るには、オプションファイルに文字セットを指定します。 たとえば、次のオプションファイル設定では、mysql を起動するたびに、接続関連の 3 つの文字セットシステム変数が koi8r に設定されます: MySQL 8.0 リファレンスマニュアル /mysql/etc/my.cnf [client] default-character-set=utf8mb4 #clientセクションを追加 [mysqld] skip-host-cache skip-name-resolve datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock secure-file-priv=/var/lib/mysql-files user=mysql pid-file=/var/run/mysqld/mysqld.pid 4. docker-compose.ymlを修正 ローカルの/mysql/etc/my.cnfをmysqlコンテナの/etc/my.cnfにマウントします。 これで、ローカルのmy.cnfに加えた変更がmysqlコンテナのmy.cnfにも反映されます。 docker-compose.yml #mysqlに関する部分のみ抜粋 mysql: image: 'mysql/mysql-server:8.0' ports: - '${FORWARD_DB_PORT:-3306}:3306' environment: MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' MYSQL_ROOT_HOST: "%" MYSQL_DATABASE: '${DB_DATABASE}' MYSQL_USER: '${DB_USERNAME}' MYSQL_PASSWORD: '${DB_PASSWORD}' MYSQL_ALLOW_EMPTY_PASSWORD: 1 volumes: - 'sailmysql:/var/lib/mysql' - './mysql/etc/my.cnf:/etc/my.cnf' # 追記 networks: - sail healthcheck: test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"] retries: 3 timeout: 5s コンテナを停止・再作成する docker-compose.ymlの変更を反映するために、コンテナを停止・再作成します。 もしサービス用のコンテナが存在している場合、かつ、コンテナを作成後にサービスの設定やイメージを変更している場合は、 docker-compose up -d を実行すると、 設定を反映するためにコンテナを停止・再作成します(マウントしているボリュームは、そのまま保持します)。 Docker-docs-ja fkjtmy@mba laravel-practice % docker compose up -d [+] Running 6/6 ⠿ Container laravel-practice_mailhog_1 Running 0.0s ⠿ Container laravel-practice_selenium_1 Running 0.0s ⠿ Container laravel-practice_meilisearch_1 Running 0.0s ⠿ Container laravel-practice_mysql_1 Running 0.0s ⠿ Container laravel-practice_redis_1 Running 0.0s ⠿ Container laravel-practice-laravel.test_1 Running 0.0s 設定が反映されたか確認する mysqlのコンテナに入り、ローカルでの修正内容が反映されているかを確認します。 $ docker exec -it [MySQLのコンテナ名] /bin/bash ///mysqlのコンテナに入る bash-4.4# cat /etc/my.cnf [client] default-character-set=utf8mb4 #clientセクションを追加 [mysqld] skip-host-cache skip-name-resolve datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock secure-file-priv=/var/lib/mysql-files user=mysql pid-file=/var/run/mysqld/mysqld.pid 反映された!! 次はmysqlに接続し、文字セットとテーブルを確認します。 mysqlの文字セットとテーブルの確認 mysql> show variables like "chara%"; +--------------------------+--------------------------------+ | Variable_name | Value | +--------------------------+--------------------------------+ | character_set_client | utf8mb4 | | character_set_connection | utf8mb4 | | character_set_database | utf8mb4 | | character_set_filesystem | binary | | character_set_results | utf8mb4 | | character_set_server | utf8mb4 | | character_set_system | utf8mb3 | | character_sets_dir | /usr/share/mysql-8.0/charsets/ | +--------------------------+--------------------------------+ 8 rows in set (0.01 sec) mysql> select * from boards; +----+-----------+-----------------+---------+---------------------+---------------------+ | id | person_id | title | message | created_at | updated_at | +----+-----------+-----------------+---------+---------------------+---------------------+ | 1 | 1 | Hello | xxxxxxx | 2022-01-11 07:32:54 | 2022-01-11 07:32:54 | | 2 | 2 | こんにちは | yyyyy | 2022-01-11 07:33:24 | 2022-01-11 07:33:24 | | 3 | 1 | good_morning | xxxxxx | 2022-01-11 09:32:31 | 2022-01-11 09:32:31 | +----+-----------+-----------------+---------+---------------------+---------------------+ 3 rows in set (0.01 sec) 解決!! character_set_client character_set_connection character_set_results の3項目がutf8mb4に変更されています。 また、テーブル2行目のtitleが「こんにちは」とひらがなで正常に表示されています。 まとめ mysqlで文字化けが起こった際は、 1. /mysql/etc/my.cnfファイルをローカルに作成 2. mysqlコンテナ内の/etc/my.cnfの内容を1.のファイルにコピー 3. ローカルの/mysql/etc/my.cnfに設定を追記 4. docker-compose.ymlを修正 の手順で修正できます。 my.cnfを作成する場所などは開発環境に合わせて適宜変更しても問題ありません。 参考文献 MySQL 8.0 リファレンスマニュアル 10.4 接続文字セットおよび照合順序 4.2.2.2 オプションファイルの使用 Docker-docs-ja
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む