Humanity

Edit the world by your favorite way

Rails で DB をデフォルトの SQLite から MySQL に変更する記録 (WSL, Debian 9)

Rails をやることになって入門してます。 Rails アプリで DB をデフォルトの SQLite から MySQL に変えようとして色々困った記録。 MySQL もあんまり触れたことなかったのでついでにインストールから。 環境はタイトルにも書いた通り Windows on Subsystem Linux (WSL) の Debian 9 です。

作業ログも兼ねたメモなので非常に冗長な書き方になっています。作業手順の記事を求めてる人は他の記事見た方がいいです。 あと日記形式になってるため口調もアレで技術文書の体はまったくなしていないので注意。

Debian 9 (Stretch) からは apt install mysql-serverMariaDB が入る

デフォルトで MariaDB が入るようになったらしい。 次の手順で無事 MySQL が入る。

# echo -e "deb http://repo.mysql.com/apt/debian/ stretch mysql-5.7\ndeb-src http://repo.mysql.com/apt/debian/ stretch mysql-5.7" > /etc/apt/sources.list.d/mysql.list
# wget -O /tmp/RPM-GPG-KEY-mysql https://repo.mysql.com/RPM-GPG-KEY-mysql
# apt-key add /tmp/RPM-GPG-KEY-mysql
# apt update
# apt install mysql-server

引用元: How to install MySQL Server on Debian Stretch – I used to be a MySQL DBA for Hire

認証でつまずく

mysqld_safe --skip-grant-tables でセーフモードで mysql を起動することにより、認証をスキップできるらしい。

$ sudo service mysql stop
$ sudo mysqld_safe --skip-grant-tables &
$ mysql -u root

入れた。 そして上記記事によると次の SQL 文を実行するとうまく行くらしい。 調べたら1個目は念のためパスワードを再設定する手順。 2個目は認証方法をパスワード認証に変える方法らしい。

> update user set password=PASSWORD("mynewpassword") where User='root';
> update user set plugin="mysql_native_password";

デフォルトだと user.plugin には unix_socket が設定されていた。

mysql> select plugin from user;
+-------------+
| plugin      |
+-------------+
| unix_socket |
+-------------+
1 row in set (0.00 sec)

上記手順でうまく行くらしいことは分かったけど、(1カラムとはいえ) テーブルを書き換えるので念のため事前に mysql テーブルをバックアップしたい。 ただ mysqldump コマンドを実行してみたらエラーになった。 -h localhost 付けなくても同じ。

$ mysqldump -u root -h localhost mysql
-- MySQL dump 10.13  Distrib 5.7.22, for Linux (x86_64)
--
-- Host: localhost    Database: mysql
-- ------------------------------------------------------
-- Server version       5.7.22

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
mysqldump: Couldn't execute 'SHOW VARIABLES LIKE 'gtid\_mode'': Table 'performance_schema.session_variables' doesn't exist (1146)

エラーメッセージでググったらどうやら mysql_upgrade で直るらしい。

$ sudo mysql_upgrade -u root
Checking if update is needed.
Checking server version.
Running queries to upgrade MySQL server.
mysql_upgrade: (non fatal) [ERROR] 1728: Cannot load from mysql.proc. The table is probably corrupted
mysql_upgrade: (non fatal) [ERROR] 1545: Failed to open mysql.event
mysql_upgrade: [ERROR] 1136: Column count doesn't match value count at row 1

これもエラー。 うーん…もういっそ select * from mysql; の結果を (プライベートな日記ブログ) に貼り付けとくか… と思って再度接続しようとしてみた。 あれ?繋がらない。 しかも Ctrl-C (SIGINT) でも Ctrl-\ (SIGQUIT) でもセーフモードで立ち上げた MySQL が停止しない。

$ mysql -u root mysql
ERROR 1524 (HY000): Plugin 'unix_socket' is not loaded
$ mysql -u root
ERROR 1524 (HY000): Plugin 'unix_socket' is not loaded
$ jobs
[1]+  Running                 sudo mysqld_safe --skip-grant-tables &
$ fg
sudo mysqld_safe --skip-grant-tables
^C

^C^C^C^C^C^C^C
^\^\^\

わからん…何も…となったので別の WSL の端末を立ち上げてそっちから pkill mysqld したら停止した。何なんだ。

気を取り直して MySQL をセーフモードで立ち上げなおして接続、先ほどの SQL 文を実行する。

mysql> update user set password=PASSWORD("hogeramypassword") where User='root';
ERROR 1054 (42S22): Unknown column 'password' in 'field list'

カラムがないと言われた。確かにない。 どうもパスワードを設定する方法を再度ググったら次の SQL 文でいけるっぽい (さっきのは古い?)。

mysql> SET PASSWORD FOR root = PASSWORD('hogeramypassword');
ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement

ヌン!!!!!!!!

まぁさっきインストールの時にパスワード設定したばっかりだしまた再設定することもないでしょ… MySQL 初心者なのでここで再設定したパスワードとさっきのパスワードの意味が違うとかないよね?とか思いつつ、まぁ普通に root ユーザのパスワードだろうと思い気にしないことにする。 なので無視して次の SQL 文を実行。

update user set plugin="mysql_native_password";

今度は成功した。 セーフモードで起動していた MySQL を停止し…また Ctrl-C で停止しなくなっていたので pkill でぶち殺した後、 service mysql start で通常起動しなおす。 そしてクライアントからログイン。

$ mysql -u root -p
Enter password:
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)

往々にして悪い予感は当たるもので、どうやらパスワードが空になっていたらしい。 なのでパスワードを打ち込む代わりに空エンターしてログイン。 そして SET PASSWORD FOR root = PASSWORD('hogeramypassword'); でパスワードの再設定をしようとした。 (最初インストール時に設定したパスワードはなんだったんだろう…)

mysql> SET PASSWORD FOR root = PASSWORD('hogeramypassword');
ERROR 1805 (HY000): Column count of mysql.user is wrong. Expected 45, found 48. The table is probably corrupted

ンーーーーー!!! (ググりつつ) これか! …って mysql_upgrade --force -uroot -p ってさっき試したやんけ!! ん? --force 付いとるな?あとさっきはセーフモードで起動した状態だったな? 試しに実行。

$ mysql_upgrade --force -uroot -p
Enter password:
Checking server version.
Running queries to upgrade MySQL server.
mysql_upgrade: [ERROR] 1136: Column count doesn't match value count at row 1
Checking system database.
mysql.column_stats                                 OK
mysql.columns_priv                                 OK
mysql.db                                           OK
mysql.engine_cost                                  OK
mysql.event                                        OK
mysql.func                                         OK
mysql.general_log                                  OK
mysql.gtid_executed                                OK
mysql.gtid_slave_pos                               OK
mysql.help_category                                OK
mysql.help_keyword                                 OK
mysql.help_relation                                OK
mysql.help_topic                                   OK
mysql.host                                         OK
mysql.index_stats                                  OK
mysql.innodb_index_stats                           OK
mysql.innodb_table_stats                           OK
mysql.ndb_binlog_index                             OK
mysql.plugin                                       OK
mysql.proc                                         OK
mysql.procs_priv                                   OK
mysql.proxies_priv                                 OK
mysql.roles_mapping                                OK
mysql.server_cost                                  OK
mysql.servers                                      OK
mysql.slave_master_info                            OK
mysql.slave_relay_log_info                         OK
mysql.slave_worker_info                            OK
mysql.slow_log                                     OK
mysql.table_stats                                  OK
mysql.tables_priv                                  OK
mysql.time_zone                                    OK
mysql.time_zone_leap_second                        OK
mysql.time_zone_name                               OK
mysql.time_zone_transition                         OK
mysql.time_zone_transition_type                    OK
mysql.user                                         OK
Upgrading the sys schema.
mysql_upgrade: [ERROR] 1136: Column count doesn't match value count at row 1
Checking databases.
sys.sys_config                                     OK
Upgrade process completed successfully.
Could not create the upgrade info file '/var/lib/mysql/mysql_upgrade_info' in the MySQL Servers datadir, errno: 13

Upgrade process completed successfully. と出ていたなんとなくいけたっぽい雰囲気がある。 なんか最後エラー出てるけど。 ので再度 SET PASSWORD FOR root = PASSWORD('hogeramypassword') を実行してみる。 さっきと全く同じエラー。

mysql> SET PASSWORD FOR root = PASSWORD('hogeramypassword');
ERROR 1805 (HY000): Column count of mysql.user is wrong. Expected 45, found 48. The table is probably corrupted

create user myapp identified by 'hogeramypassword' とかでもこれが出る。 うーん… もう一旦空パスワードで運用するか… とりあえず MySQL の設定だけで休日が終わりそうな気がしたので一旦そういうことにした。

Rails の DB 設定を変更

まず MySQL の adapter をインストールする必要があるので、 コンパイルのためにヘッダーをインストールする。

$ sudo apt install libmysqlclient-dev

次に Gemfile の

gem 'sqlite3'

の部分を

gem 'mysql2'

に変更。

そして

bundle install

する。

無事インストールできたら、次に DB 接続設定を変えるため、 config/database.yml を変更。

Before

# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  database: db/production.sqlite3

After

# MySQL version 2.x
#   gem install mysql2
#
#   Ensure the MySQL 2 gem is defined in your Gemfile
#   gem 'mysql2'
#
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock

development:
  <<: *default
  database: myapp_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: myapp_production

接続試験も兼ねて rails db:create, rails db:migrate を実行してデータベースとテーブルを作成させてみる。 実行した後テーブルが作成されているのを確認するのと、 わざと mysql サービスを落として rails db:migrate して参照先が間違ってないかのチェック。 無事うまく行ってるっぽい。