20200814のMySQLに関する記事は10件です。

mariadb(mysql)起動しなくなった場合

OS: centos7

journalctl -xe

 8月 14 23:09:01 cent74 mariadbd[8773]: 2020-08-14 23:09:01 0 [Note] InnoDB: Using SSE4.2 crc32 instructions
 8月 14 23:09:01 cent74 mariadbd[8773]: 2020-08-14 23:09:01 0 [Note] InnoDB: Initializing buffer pool, total size = 134217728, chunk size = 134
 8月 14 23:09:01 cent74 mariadbd[8773]: 2020-08-14 23:09:01 0 [Note] InnoDB: Completed initialization of buffer pool
 8月 14 23:09:01 cent74 mariadbd[8773]: 2020-08-14 23:09:01 0 [Note] InnoDB: If the mysqld execution user is authorized, page cleaner thread pr
 8月 14 23:09:01 cent74 mariadbd[8773]: 2020-08-14 23:09:01 0 [ERROR] InnoDB: Upgrade after a crash is not supported. The redo log was created
 8月 14 23:09:01 cent74 mariadbd[8773]: 2020-08-14 23:09:01 0 [ERROR] InnoDB: Plugin initialization aborted with error Generic error
 8月 14 23:09:02 cent74 mariadbd[8773]: 2020-08-14 23:09:02 0 [Note] InnoDB: Starting shutdown...
 8月 14 23:09:02 cent74 mariadbd[8773]: 2020-08-14 23:09:02 0 [ERROR] Plugin 'InnoDB' init function returned error.
 8月 14 23:09:02 cent74 mariadbd[8773]: 2020-08-14 23:09:02 0 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
 8月 14 23:09:02 cent74 mariadbd[8773]: 2020-08-14 23:09:02 0 [Note] Plugin 'FEEDBACK' is disabled.
 8月 14 23:09:02 cent74 mariadbd[8773]: 2020-08-14 23:09:02 0 [ERROR] Unknown/unsupported storage engine: InnoDB
 8月 14 23:09:02 cent74 mariadbd[8773]: 2020-08-14 23:09:02 0 [ERROR] Aborting
 8月 14 23:09:02 cent74 systemd[1]: mariadb.service: main process exited, code=exited, status=1/FAILURE
 8月 14 23:09:02 cent74 systemd[1]: Failed to start MariaDB 10.5.5 database server.
-- Subject: Unit mariadb.service has failed
-- Defined-By: systemd

上記ファイルを削除

/var/lib/mysql/ib_logfile0
/var/lib/mysql/ib_logfile1
/var/lib/mysql/ib_logfile101

再起動

systemctl restart mariadb
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Docker】コンテナ構築時に、MySQLデータベースが立ち上がらない

エラー内容

ERROR: for db  Cannot start service db: Ports are not available: listen tcp 0.0.0.0:3306: bind: address already in use

どうやら、すでにポート番号3306が使われている様子

まずは、プロセス確認

$ sudo lsof -i -P | grep "LISTEN"
rapportd    315     user    4u  IPv4 0xb08837e7170ea82d      0t0    TCP *:59397 (LISTEN)
rapportd    315     user    5u  IPv6 0xb08837e7098fcf3d      0t0    TCP *:59397 (LISTEN)
com.docke 19101     user   10u  IPv4 0xb08837e717a7d30d      0t0    TCP localhost:56362 (LISTEN)
com.docke 19102     user   50u  IPv6 0xb08837e709900c7d      0t0    TCP *:8080 (LISTEN)
mysqld    20618     _mysql   21u  IPv4 0xb08837e7180b7e4d      0t0    TCP localhost:3306 (LISTEN)

とりま、ID指定して停止。

$ kill -9 20618 

$ docker-compose up -d --build
ERROR: for db  Cannot start service db: Ports are not available: listen tcp 0.0.0.0:3306: bind: address already in use

まだダメ、、、。
mysqldはまだ起動していた。

そこで、思い出した。

他のプロジェクトで、

$ mysql.server start

していたことを。

エラー解決

mysql自体を止めましょう

$ mysql.server stop
$ docker-compose up -d --build

Starting db ... done

成功!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「MySQL 8.0]大量のレコードをDELETEする

[MySQL]大量のレコードをDELETEする という記事を書いたのは 2016 年のこと。使用した MySQL も 5.6.30 でした。MySQL も今では 8.0 系列が登場していますので、同じクエリを MySQL 8.0 環境で動かしてみました。

使用したのは MySQL 8.0.21 です。

root@localhost [(none)]> select version();
+-----------+
| version() |
+-----------+
| 8.0.21    |
+-----------+
1 row in set (0.00 sec)

開発機で試したので root ユーザになってますがご容赦を。

テスト用テーブル作成

0から9の値が均等に配置された1千万レコード。いろいろ試すためプライマリキー、インデックス、外部制約などを付けてます。以後のテストでは、毎回テーブルを DROP して作り直してます。

root@localhost [test]> CREATE TABLE seed (
    ->   value INTEGER UNIQUE
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.29 sec)

root@localhost [test]> INSERT INTO seed (value) VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9);
Query OK, 10 rows affected (0.04 sec)
Records: 10  Duplicates: 0  Warnings: 0

root@localhost [test]> DROP TABLE IF EXISTS huge;
Query OK, 0 rows affected, 1 warning (0.01 sec)

root@localhost [test]> CREATE TABLE huge (
    ->   id INTEGER PRIMARY KEY AUTO_INCREMENT,
    ->   number INTEGER,
    ->   string VARCHAR(255),
    ->   INDEX num_idx (number),
    ->   INDEX str_idx (string),
    ->   CONSTRAINT num_fk FOREIGN KEY (number) REFERENCES seed(value)
    -> ) ENGINE=InnoDB;

root@localhost [test]> INSERT INTO huge SELECT NULL, s1.value, s1.value FROM seed AS s1, seed AS s2, seed AS s3, seed AS s4, seed AS s5, seed AS s6, seed AS s7;
Query OK, 10000000 rows affected (6 min 38.02 sec)
Records: 10000000  Duplicates: 0  Warnings: 0

root@localhost [test]> SELECT COUNT(*) FROM huge;
+----------+
| COUNT(*) |
+----------+
| 10000000 |
+----------+
1 row in set (0.89 sec)

root@localhost [test]> SELECT number, COUNT(*) FROM huge GROUP BY number;
+--------+----------+
| number | COUNT(*) |
+--------+----------+
|      0 |  1000000 |
|      1 |  1000000 |
|      2 |  1000000 |
|      3 |  1000000 |
|      4 |  1000000 |
|      5 |  1000000 |
|      6 |  1000000 |
|      7 |  1000000 |
|      8 |  1000000 |
|      9 |  1000000 |
+--------+----------+
10 rows in set (4.10 sec)

全DELETE

root@localhost [test]> DELETE FROM huge;
Query OK, 10000000 rows affected (18 min 21.52 sec)

TRUNCATE

root@localhost [test]> TRUNCATE TABLE huge;
Query OK, 0 rows affected (2.27 sec)

一部DELETE

root@localhost [test]> DELETE FROM huge WHERE number=0;
Query OK, 1000000 rows affected (1 min 14.75 sec)

一部DELETE(LIMIT指定)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (4.19 sec)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (4.88 sec)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (5.27 sec)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (8.73 sec)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (9.62 sec)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (10.43 sec)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (11.02 sec)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (12.21 sec)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (13.78 sec)

root@localhost [test]> DELETE FROM huge WHERE number=0 LIMIT 100000;
Query OK, 100000 rows affected (13.87 sec)

一部DELETE(INDEX一時削除)

root@localhost [test]> ALTER TABLE huge DROP INDEX str_idx;
Query OK, 0 rows affected (0.89 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@localhost [test]> DELETE FROM huge WHERE number=0;
Query OK, 1000000 rows affected (1 min 7.10 sec)

root@localhost [test]> ALTER TABLE huge ADD INDEX str_idx (string);
Query OK, 0 rows affected (2 min 5.37 sec)
Records: 0  Duplicates: 0  Warnings: 0

一部DELETE(外部キー制約一時解除)

root@localhost [test]> ALTER TABLE huge DROP FOREIGN KEY num_fk;
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@localhost [test]> DELETE FROM huge WHERE number=0;
Query OK, 1000000 rows affected (1 min 12.24 sec)

root@localhost [test]> ALTER TABLE huge ADD CONSTRAINT num_fk FOREIGN KEY (number) REFERENCES seed(value);
Query OK, 9000000 rows affected (4 min 15.43 sec)
Records: 9000000  Duplicates: 0  Warnings: 0

別テーブルの作成

root@localhost [test]> CREATE TABLE huge_copy LIKE huge;
Query OK, 0 rows affected (0.06 sec)

root@localhost [test]> ALTER TABLE huge_copy ADD CONSTRAINT num_fk_copy FOREIGN KEY (number) REFERENCES seed(value);
Query OK, 0 rows affected (0.26 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@localhost [test]> INSERT INTO huge_copy SELECT * FROM huge WHERE number<>0;
Query OK, 9000000 rows affected (5 min 55.15 sec)
Records: 9000000  Duplicates: 0  Warnings: 0

root@localhost [test]> RENAME TABLE huge TO huge_old, huge_copy TO huge;
Query OK, 0 rows affected (0.71 sec)

root@localhost [test]> DROP TABLE huge_old;
Query OK, 0 rows affected (0.67 sec)

別テーブルの作成(INDEX一時削除)

root@localhost [test]> CREATE TABLE huge_copy LIKE huge;
Query OK, 0 rows affected (0.08 sec)

root@localhost [test]> ALTER TABLE huge_copy ADD CONSTRAINT num_fk_copy FOREIGN KEY (number) REFERENCES seed(value);
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@localhost [test]> ALTER TABLE huge_copy DROP INDEX str_idx;
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@localhost [test]> INSERT INTO huge_copy SELECT * FROM huge WHERE number<>0;
Query OK, 9000000 rows affected (4 min 47.31 sec)
Records: 9000000  Duplicates: 0  Warnings: 0

root@localhost [test]> ALTER TABLE huge_copy ADD INDEX str_idx (string);
Query OK, 0 rows affected (1 min 8.52 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@localhost [test]> RENAME TABLE huge TO huge_old, huge_copy TO huge;
Query OK, 0 rows affected (0.10 sec)

root@localhost [test]> DROP TABLE huge_old;
Query OK, 0 rows affected (0.74 sec)

パーティショニングDROP

root@localhost [test]> DROP TABLE IF EXISTS p_huge;
Query OK, 0 rows affected, 1 warning (0.02 sec)

root@localhost [test]> CREATE TABLE p_huge (
    ->  number INTEGER,
    ->  string VARCHAR(255),
    ->  INDEX num_idx (number),
    ->  INDEX str_idx (string)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.08 sec)

root@localhost [test]> INSERT INTO p_huge SELECT s1.value, s1.value FROM seed AS s1, seed AS s2, seed AS s3, seed AS s4, seed AS s5, seed AS s6, seed AS s7;
Query OK, 10000000 rows affected (5 min 8.32 sec)
Records: 10000000  Duplicates: 0  Warnings: 0

root@localhost [test]> ALTER TABLE p_huge PARTITION BY LIST(number) (
    ->   PARTITION num_0 VALUES IN (0),
    ->   PARTITION num_1 VALUES IN (1),
    ->   PARTITION num_2 VALUES IN (2),
    ->   PARTITION num_3 VALUES IN (3),
    ->   PARTITION num_4 VALUES IN (4),
    ->   PARTITION num_5 VALUES IN (5),
    ->   PARTITION num_6 VALUES IN (6),
    ->   PARTITION num_7 VALUES IN (7),
    ->   PARTITION num_8 VALUES IN (8),
    ->   PARTITION num_9 VALUES IN (9)
    -> );
Query OK, 10000000 rows affected (3 min 47.23 sec)
Records: 10000000  Duplicates: 0  Warnings: 0

root@localhost [test]> ALTER TABLE p_huge DROP PARTITION num_0;
Query OK, 0 rows affected (2.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

root@localhost [test]> ALTER TABLE p_huge ADD PARTITION (
    ->   PARTITION num_0 VALUES IN ( 0 )
    -> );
Query OK, 0 rows affected (0.09 sec)
Records: 0  Duplicates: 0  Warnings: 0

総評

MySQL 5.6.30 と比較してみました。ちなみに前回と今回で使用したハードウェアは同じです。

テスト項目 MySQL 5.6.30 MySQL 8.0.21
全DELETE 28分57.90秒 18分21.52秒
TRUNCATE 0.41秒 2.27秒
一部DELETE 3分15.38秒 1分14.75秒
一部DELETE(LIMIT指定) 2分47.27秒 1分34秒
一部DELETE(INDEX一時削除) 2分32.75秒 3分13.36秒
一部DELETE(外部キー制約一時解除) 3分16.11秒 5分27.74秒
別テーブルの作成 2分12.60秒※ 5分56.87秒
別テーブルの作成(INDEX一時削除) 2分40.57秒 5分57秒
パーティショニングDROP 0.08秒 2.27秒

※ MySQL 5.6 では外部キー制約のコピーが漏れていたので直接比較はできない。

こうしてみると 8.0 系になって大量 DELETE はかなり速くなってますね。そして、下手に小細工を弄するよりも素直な手順でやった方がよさそうですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SQL]DELETE文でデータを削除

DELETE文でデータを削除

テーブルのデータを削除するときはDELETE文を使います。

DELETE FROM テーブル名 WHERE 条件文;

SELECT文でuserテーブルのデータを確認。

mysql> select * from user;
+------+---------------+----------------------------------------------------+
| id   | name          | prefecture | address                              |                             
+------+---------------+----------------------------------------------------+
|    1 | テスト        | 東京都      | 東京都千代田区丸の内1丁目                 |                             
|    2 | テスト1         | 滋賀県      | 滋賀県彦根市金剛寺町2-16ザ金剛寺町313      |                            
|    3 | テスト2         | 大阪府      | 東京都練馬区豊玉南4-11                   |
|    4 | テスト3         | 北海道      | 北海道千歳市千代田町2-3千代田町シティ102   |                          
|    5 | テスト4         | 東京都      | 東京都小平市回田町2-12-17フォレスト回田町202 |                           
+------+---------------+---------------------------+------------------------+

データを削除していきます。
「id > 3」とすると、idが3より大きいデータが対象になります。

mysql> DELETE FROM meibo WHERE id > 3;
Query OK, 2 rows affected (0.19 sec)

データが削除されているか確認します。

mysql> select * from user;
+------+---------------+----------------------------------------------------+
| id   | name          | prefecture | address                              |                             
+------+---------------+----------------------------------------------------+
|    1 | テスト        | 東京都      | 東京都千代田区丸の内1丁目                 |                             
|    2 | テスト1         | 滋賀県      | 滋賀県彦根市金剛寺町2-16ザ金剛寺町313      |                            
|    3 | テスト2         | 大阪府      | 東京都練馬区豊玉南4-11                   |                          
+------+---------------+---------------------------+------------------------+
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SQL]UPDATE文でデータを更新

UPDATE文でデータを更新

すでに挿入済みのデータを更新するときは、UPDATE文を使います。

UPDATE テーブル名 SET カラム名1 = 値1, カラム名2 = 値2 WHERE 条件文; 

現在のテーブルデータは以下です。

mysql> select * from user;
+------+---------------+----------------------------------------------------+
| id   | name          | prefecture | address                              |                             
+------+---------------+----------------------------------------------------+
|    1 | テスト        | 東京都      | 東京都千代田区丸の内1丁目                 |                             
|    2 | テスト1         | 滋賀県      | 滋賀県彦根市金剛寺町2-16ザ金剛寺町313      |                            
|    3 | テスト2         | 東京都      | 東京都練馬区豊玉南4-11                   |
|    4 | テスト3         | 北海道      | 北海道千歳市千代田町2-3千代田町シティ102   |                          
|    5 | テスト4         | 東京都      | 東京都小平市回田町2-12-17フォレスト回田町202 |                           
+------+---------------+---------------------------+------------------------+

上記テーブルの「id=2」の「prefecture」を変更してみます。

mysql> UPDATE user SET prefecture = 大阪府 WHERE id = 2;
Query OK, 1 row affected (0.17 sec)
Rows matched: 1  Changed: 1  Warnings: 0

更新できているか確認してみます。

mysql> select * from user;
+------+---------------+----------------------------------------------------+
| id   | name          | prefecture | address                              |                             
+------+---------------+----------------------------------------------------+
|    1 | テスト        | 東京都      | 東京都千代田区丸の内1丁目                 |                             
|    2 | テスト1         | 滋賀県      | 滋賀県彦根市金剛寺町2-16ザ金剛寺町313      |                            
|    3 | テスト2         | 大阪府      | 東京都練馬区豊玉南4-11                   |
|    4 | テスト3         | 北海道      | 北海道千歳市千代田町2-3千代田町シティ102   |                          
|    5 | テスト4         | 東京都      | 東京都小平市回田町2-12-17フォレスト回田町202 |                           
+------+---------------+---------------------------+------------------------+
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLで複数のDBをまたいで使用する

概要

サービスが大きくなったりしてDBが増えたときに、複数のDBをまたいで使用する場合の書き方です。

環境

PHP 5.3.29
FuelPHP 1.7.3
MySQL 5.5.61

本題

方法1

まず、FuelPHPのfuel/app/config以下にあるdb.phpに設定を書きます。書くファイルはそれぞれの環境に応じたものにします。(開発環境であればdevelopment/db.php
ここに複数の設定を書けばクエリを発行する際に各DBを指定して使用できます。

db.php
<?php
return array{
  'db01' => array(
    'type'        => 'pdo',
    'connection'  => array(
      'dsn'        => 'mysql:host=localhost;dbname=db01',
      'username'   => '',
      'password'   => '',
    ),
  ),
  'db02' => array(
    'type'        => 'pdo',
    'connection'  => array(
      'dsn'        => 'mysql:host=localhost;dbname=db02',
      'username'   => '',
      'password'   => '',
    ),
  ),
  'db03' => array(
    'type'        => 'pdo',
    'connection'  => array(
      'dsn'        => 'mysql:host=localhost;dbname=db03',
      'username'   => '',
      'password'   => '',
    ),
  ),
);
?>

各パラメータは各々の環境に合わせます。

次に、モデルにクエリを発行する文を書きます。

examplemodel.php
<?php
namespace Example;
class Model_Examplemodel extends \Model
{
  public static function get_example()
  {
    $get = \DB::select()
    ->from('tablename')
    ->execute('db01')
    return $get;
  }
}
?>

このようにexecute()の引数にdb.phpで定義した文字を入れればそのDBを参照してくれます。

方法2

セレクトするテーブルの情報にDB名を加える形でも可能です。

examplemodel.php
<?php
namespace Example;
class Model_Examplemodel extends \Model
{
  public static function get_example()
  {
    $get = \DB::select()
    ->from('db01.tablename')
    ->execute()
    return $get;
  }
}
?>

joinする場合

joinする場合は、方法1をとるとすべてそのDBにあるテーブルが参照されます。
ですので、db01・db02それぞれからテーブルを参照する必要がある場合は方法2、もしくは両方を合わせて使う必要があります。

examplemodel.php
<?php
namespace Example;
class Model_Examplemodel extends \Model
{
  public static function get_example()
  {
    $get = \DB::select()
    ->from('db01.table01')
    ->join('db02.table02')
    ->on('table01.column01', '=', 'table02.column01')
    ->execute()
    return $get;
  }
}
?>

カラム名が被っている場合はその点にも注意します。

終わりに

FuelPHPを書いていて、発行されたクエリが簡単にわかる方法はないかなーと思って探しています。知っている方いましたらコメントいただけるとすごく助かります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLdumpでバックアップ【warning】using a password on the command line interface can be insecure

MySQLdumpでバックアップ【warning】using a password on the command line interface can be insecure

MySQLdumpでバックアップする基本構文でパスワードが直書きされているとのエラーが。
どうやらSQLコマンドにパスワードを直書きすると注意されるらしい。

mysqldumpの基本構文は以下。

mysqldumdp -u[ユーザー名] -p[パスワード] -h[ホスト名] [データベース名] [テーブル名1], [テーブル名2],...[オプション] > [保存先ファイル名]

調べたところ、パスワードを外部ファイルに保存するなどの方法もありましたが、次の方法でOK

mysqldump -u[ユーザー名] -p -h[ホスト名] [データベース名] [テーブル名1], [テーブル名2],...[オプション] > [保存先ファイル名]
パスワード欄は空欄にして打ち込みENTERを押すと

ENTER passwordという表記になるのでそこでパスワード入力。

参考

https://proengineer.internous.co.jp/content/columnfeature/6793#section100

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerのMySQLへのinsertでError Code: 1366. Incorrect string value: 'xx' for column 'name' at row 1

事象

日本語を含むinsert分で上記エラーが発生。

対応

以下を確認すると文字コードがlatin1になっているのが原因。

mysql> status;
--------------
mysql  Ver 14.14 Distrib 5.7.31, for osx10.15 (x86_64) using  EditLine wrapper

Connection id:      174
Current database:   xxx
Current user:       root@xxx
SSL:            Cipher in use is DHE-RSA-AES256-SHA
Current pager:      stdout
Using outfile:      ''
Using delimiter:    ;
Server version:     5.7.26 MySQL Community Server (GPL)
Protocol version:   10
Connection:     127.0.0.1 via TCP/IP
Server characterset:    latin1
Db     characterset:    latin1
Client characterset:    utf8
Conn.  characterset:    utf8
TCP port:       13307
Uptime:         2 days 3 hours 37 min 13 sec

Threads: 3  Questions: 2106  Slow queries: 0  Opens: 219  Flush tables: 1  Open tables: 212  Queries per second avg: 0.011
--------------

以下で確認したところmy.cnfがない。

docker container exec -it xxx bash
cat /etc/mysql/conf.d/my.cnf

Dockerfileが間違っていた。(mysqlではなくmusql)

ADD ./Docker/mysql/conf.d/my.cnf /etc/musql/conf.d/my.cnf

上記修正をした上で、以下でコンテナを再作成。
(再起動でも良いのだけど、もう一度マイグレーションをロールバックして、再実行するのが手間だと感じたので)

docker-compose down --rmi all --volumes
docker-compose up -d

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-compose を用いて Apache・PHP・MySQL の開発環境を構築してみた

はじめに

docker-compose を用いて Apache・PHP・MySQL の開発環境を構築してみた備忘録になります。

バージョン情報

docker-compose はインストールされていることが前提になります。
今回は以下のバージョンでの動きになります。

$ docker-compose -v
docker-compose version 1.26.2, build eefe0d31

PHP のバージョンは 5.4、MySQL のバージョンは 5.5 の環境を構築します。
Apache のバージョンは特に気にしていません。

ディレクトリ構造

ディレクトリ構造は以下のようになります。

.
├── config
│   ├── mysql
│   │   ├── Dockerfile
│   │   ├── initdb.d
│   │   │   └── init.sql
│   │   └── my.cnf
│   └── php
│       ├── Dockerfile
│       ├── apache2.conf
│       ├── php.ini
│       └── sites
│           ├── 000-default.conf
│           └── default-ssl.conf
├── data
├── docker-compose.yml
└── html
    └── test
        ├── connect.php
        └── index.php

GitHub にもあげました。ご参考まで。

設定

各サービスに以下のような設定ファイルがそれぞれあると思いますが、それらを変更したものを動かしたいと思います。

Apache の設定

今回は DocumentRoot を変更してみます。

公式ドキュメント をみると以下のように Dockerfile を記述することで DocumentRoot を変更できるようです。

b.png

いろいろな記事をみるとコンテナから 000-default.conf などの設定ファイルをホスト側にもってきてそれを修正した後、Dockerfile の COPY でコンテナ側にコピーするようでした。

やはり公式ドキュメントをみるのが一番ですね。

公式ドキュメントのように行ったらコンテナが再起動ループに陥ってしまいました。

※ 理由をよく調べたら php:5.5-apache 以降であればそれでよかったらしいです。

なので、php:5.4-apache のイメージからコンテナを立ち上げて、元ファイルをホスト側にコピーして、それを編集することにしました。

# とりあえず php:5.4-apache のコンテナを動かしてログインする
$ docker image pull php:5.4-apache
$ docker container run --name php54apache -d php:5.4-apache
$ docker exec -it php54apache /bin/bash

# 設定ファイルを確認 ( php:5.4-apache 以前は apache2.conf の修正も必要)
$ ls /etc/apache2/apache2.conf
httpd.conf

$ ls /etc/apache2/sites-available
000-default.conf  default-ssl.conf

# コンテナを抜ける
$ exit

# ホスト側にコピー
$ docker container cp php54apache:/etc/apache2/apache2.conf ./config/php
$ docker container cp php54apache:/etc/apache2/sites-available/000-default.conf ./config/php/sites
$ docker container cp php54apache:/etc/apache2/sites-available/default-ssl.conf ./config/php/sites

これらの apache2.conf000-default.confdefault-ssl.conf に記述してある DocumentRoot を変更します。

今回は以下のように変更しました。

# DocumentRoot /var/www/html
DocumentRoot /var/www/html/test

これで DocumentRoot の変更の準備は完了です。

PHP の設定

PHP は php.ini によって設定を行います。

公式ドキュメント をみると以下のように記述することで php.ini を指定するらしいです。

a.png

しかし、コンテナを立ち上げてログインしてみても、 php.ini-developmentphp.ini-produciton のようなファイルは存在しませんでした。

おそらく Apache の設定でもそうであったように公式のドキュメントは過去のバージョンまで挙動は保証していないようです。

サンプルにあるような php:7.4-fpm-alpine ならそれで良さそうですね。

今回は php.ini-developmentphp.ini-produciton については GitHub で公開してあったのでそちらの php.ini-development を元に php.ini を作成することにしました。

php.ini の修正についてはこちらを参照して、ロケーションや言語の設定を修正しました。

MySQL の設定ファイルの変更

MySQL の設定ファイルは my.cnf によって行います。

MySQL の設定ファイルの my.cnf は 公式ドキュメント によると /etc/mysql/my.cnf に配置してあるとのことでした。

c.png

なのでそちらをローカルホストに持ってきて、適宜修正していきたいと思います。

# とりあえず mysql:5.5 のコンテナを動かしてログインする
$ docker image pull mysql:5.5
$ docker container run -e MYSQL_ROOT_PASSWORD=sample_pw --name mysql55 -d mysql:5.5
$ docker exec -it mysql55 /bin/bash

# /etc/mysql/my.cnf を確認
$ ls /etc/mysql/my.cnf
/etc/mysql/my.cnf

# コンテナを抜ける
$ exit

# my.cnf をホスト側にコピー
$ docker container cp mysql55:/etc/mysql/my.cnf ./config/mysql/

あとは環境に合わせて修正して、my.cnf を作成してください。

※ ちなみに今回は my.cnf の修正は行っていません。

Dockerfile の作成

Dockerfile は php:5.4-apachemysql:5.5 のイメージについて作成しました。

php:5.4-apache

config/php/Dockerfile
# image
FROM php:5.4-apache

# Set php.ini
COPY ./php.ini /usr/local/etc/php/

# Set apache conf (Before tag:5.4-apache)
COPY ./apache2.conf /etc/apache2/
COPY ./sites/*.conf /etc/apache2/sites-available/

# Set apache conf (After tag:5.5-apache)
# ENV APACHE_DOCUMENT_ROOT /var/www/html/test
# RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
# RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

# Install MySQL connection module
RUN apt-get update \
  && apt-get install -y libpq-dev \
  && docker-php-ext-install pdo_mysql pdo_pgsql mysqli mbstring

ここでは「DocumentRoot の変更」、「php.ini の配置」、「MySQL と疎通するためのモジュールを追加」しています。

mysql:5.5

config/mysql/Dockerfile
# image
FROM mysql:5.5

# Set my.cnf
COPY ./my.cnf /etc/mysql/conf.d/

# Set Japanese
RUN apt-get update && apt-get install -y \
  locales \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*
RUN sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen \
  && locale-gen
ENV LANG ja_JP.UTF-8
CMD ["mysqld", "--character-set-server=utf8", "--collation-server=utf8_unicode_ci"]

デフォルトのままであると MySQL にログインした後に日本語の入力ができなかったため、ここでは日本語を入力可能にするような設定をおこなています。

MySQL の日本語の設定は こちら を参照しました。

MySQL の初期データの投入

docker-compose 起動時に MySQL に初期データを投入してみたく、以下のような SQL を用意しました。

init.sql
DROP TABLE IF EXISTS sample_table;

CREATE TABLE sample_table (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name TEXT NOT NULL
) charset=utf8;

INSERT INTO sample_table (name) VALUES ("太郎"),("花子"),("令和");

コンテナ起動後にここで記述した SQL が実行されていることを確認します。

docker-compose.yml の作成

以下のように docker-compose.yml を作成しました。

docker-compose.yml
version: '3.8'

services:

  # PHP Apache
  php-apache:
    build: ./config/php
    ports:
      - "8080:80"
    volumes:
      - ./html:/var/www/html
    restart: always
    depends_on:
      - mysql

  # MySQL
  mysql:
    build: ./config/mysql
    ports:
      - 3306:3306
    volumes:
      - ./config/mysql/initdb.d:/docker-entrypoint-initdb.d
      - ./data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: sample_root_passward
      MYSQL_DATABASE: sample_db
      MYSQL_USER: sample_user
      MYSQL_PASSWORD: sample_pass

MySQL のデータの永続化について

MySQL のデータディレクトリは /var/lib/mysql であり、 - ./data:/var/lib/mysql とあるようにホスト側の ./data ディレクトリにマウントすることによってデータを永続化しております。

試してみたところ docker-compose stopdocker-compose down してもデータが永続化されていることが確認できました。

docker-compose 使い方

いよいよ docker-compose を動かします。

docker-compose 起動

まずはなにも起動されていないことを確認します。

$ docker-compose ps
Name   Command   State   Ports
------------------------------

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

今回はわかりやすいようにコンテナも空の状態にしておきます。

docker-compose.yml が存在する階層で docker-compose up -d コマンドを実行することで、docker-compose.yml の記述をもとにコンテナが作成されます。

※ docker-compose のコマンドの buildup の違いやオプションについてはこちらがわかりやすかったです。

# image の作成
$ docker-compose build --no-cache
Successfully tagged docker-compose-sample_php-apache:latest

# コンテナの構築・起動
$ docker-compose up -d
Creating network "docker-compose-sample_default" with the default driver
Creating docker-compose-sample_mysql_1 ... done
Creating docker-compose-sample_php-apache_1 ... done

# docker-compose の確認
$ docker-compose ps
               Name                            Command             State           Ports
-------------------------------------------------------------------------------------------------
docker-compose-sample_mysql_1        docker-entrypoint.sh mysqld   Up      0.0.0.0:3306->3306/tcp
docker-compose-sample_php-apache_1   apache2-foreground            Up      0.0.0.0:8080->80/tcp

# コンテナの確認
$ docker container ls
CONTAINER ID        IMAGE                              COMMAND                  CREATED             STATUS              PORTS                    NAMES
26b0cec2daad        docker-compose-sample_php-apache   "apache2-foreground"     3 minutes ago       Up 3 minutes        0.0.0.0:8080->80/tcp     docker-compose-sample_php-apache_1
eaae044f4bba        mysql:5.5                          "docker-entrypoint.s…"   3 minutes ago       Up 3 minutes        0.0.0.0:3306->3306/tcp   docker-compose-sample_mysql_1

コンテナが動いていることが確認できました。

PHP-Apache コンテナの確認

DocumentRoot の直下に配置する、php:5.4-apache コンテナの疎通確認用のページとして以下のようなファイルを用意しました。

html/test/index.php
<?php
echo  __DIR__;
phpinfo();

http://localhost:8080 にアクセスすると以下のように表示されるはずです。

echo __DIR__; はその PHP ファイルが置かれている絶対パスを出力するので DocumentRoot の変更が行われていることが確認できます。

また、php.ini についてオリジナルものから修正を加えていれば、phpinfo(); の出力でそちらも反映されていることも確認できるかと思います。

f.png

MySQL コンテナの確認

mysql:5.5 のイメージで作成されたコンテナにログインして、初期データが投入されていることを確認します。

shell
# コンテナにログイン
$ docker exec -it eaae044f4bba /bin/bash

# MySQL にログイン
$ mysql -p
Enter password:

パスワードを求められるので docker-compose.yml で MYSQL_ROOT_PASSWORD の環境変数に登録した sample_root_passward と入力するとログインできます。

docker-compose.yml に登録した MYSQL_DATABASEMYSQL_USER が登録されていることを確認します。

mysql> select user, host from mysql.user;

+-------------+-----------+
| user        | host      |
+-------------+-----------+
| root        | %         |
| sample_user | %         |
| root        | localhost |
+-------------+-----------+
3 rows in set (0.00 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sample_db          |
+--------------------+
4 rows in set (0.00 sec)

sample_usersample_db があることが確認できました。

続いて init.sql で作成したテーブルとカラムについても確認してみます。

mysql> use sample_db;
Database changed

mysql> show tables;
+---------------------+
| Tables_in_sample_db |
+---------------------+
| sample_table        |
+---------------------+
1 row in set (0.00 sec)

mysql> select * from sample_table;
+----+--------+
| id | name   |
+----+--------+
|  1 | 太郎 |
|  2 | 花子 |
|  3 | 令和 |
+----+--------+
3 rows in set (0.00 sec)

テーブルとカラムについても問題なく確認できました。

PHP-Apache から MySQL コンテナへの疎通確認

PHP-Apache から MySQL コンテナへの疎通確認用のページとして以下のようなファイルを用意しました。

html/test/connect.php
<?php
try {
    // host=XXXの部分のXXXにはmysqlのサービス名を指定します
    $dsn = 'mysql:host=mysql;dbname=sample_db;';
    $db = new PDO($dsn, 'sample_user', 'sample_pass');

    $sql = 'SELECT * FROM sample_table;';
    $stmt = $db->prepare($sql);
    $stmt->execute();
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    var_dump($result);
} catch (PDOException $e) {
    echo $e->getMessage();
    exit;
}

※ 注意点として、ホスト名は localhost127.0.0.1 ではなく、docker-compose.yml でサービス名として指定してた mysql を使用します。

http://localhost:8080/connect.php にアクセスすると以下のように表示されればが PHP-Apache から MySQL コンテナへの疎通がうまく行っています。

g.png
さきほど、初期投入されたデータが確認できますね。

docker-compose 停止・削除

以下のコマンドで 停止・削除 を行います。

# コンテナを停止
$ docker-compose stop

# コンテナを停止し、そのコンテナとネットワークの削除
$ docker-compose down

# コンテナを停止し、そのコンテナとネットワークを削除、さらにイメージも削除
$ docker-compose down --rmi all --volumes

./config/mysql/initdb.d/init.sql の初期データ投入からやり直したい時などは docker-compose down --rmi all --volumes してから ./data ディレクトリの中身も削除して docker-compose up -d でコンテナの構築・起動からやり直してください。

まとめ

以下を自動化する docker-compose の開発環境構築を行いました。

  • DocumentRoot の変更
  • php.ini の変更
  • my.cnf の変更
  • MySQL への初期データの投入
  • MySQL のデータの永続化
  • PHP-Apache コンテナから MySQL コンテナへの疎通

つまり、これらの環境がワンライナーで構築できるようになりました。

学んだこと

今回初めて docker-compose で開発環境を構築してみて学んだことを羅列します。

  • 公式ドキュメント(DockerHub)は読んだ方がいい
    • Qiita よりも DockerHub を先に見た方がよさそうです。
  • docker-compose.yml と Dockerfile はかき分ける
    • 一度変更すればコンテナ起動後に変更のない設定ファイルなどは Dockerfile で COPY でコンテナ側にファイルを配置して、コンテナ起動後に変更のあるものは docker-compose.yml でマウントさせるという書き分けがよさそうです。
  • Dockerfile をいきなり書くのは難しい
    • 以下の手順で Dockerfile を書くと良いです。
      • ベースイメージを決める
      • ベースイメージのコンテナ内で作業しつつ、うまくいった処理をメモ
      • 全て成功したらDockerfileを作成

以上になります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLのトリガーってなんなん?

沈黙?それが正しい答えなんか?

ぼく「つまり、トリガーっていうのがストアドプロシージャーってことなんですか?」

先輩「・・・・・・。」

ぼく「(・・・沈黙?それが正しい答えなんか。)」
ぼく「(いや、流石にそれが答えじゃないことぐらいは分かるで・・・。)」
ぼく「(多分、エンジニアに必要なのは洞察力やということを暗に教えてくれてるんやろうな・・・。)」
ぼく「(ほんまにええ先輩やで!)」
ぼく「わかりました!とりあえずやってみます!」

先輩「はい。」

とにかく調べてみる。

ぼく「ほーん・・・。」
ぼく「トリガーいうんはDBのテーブルにあるデータが更新されたときに走るプロシージャのことなんか」
ぼく「データの更新ってINSERT,UPDATE,DELETEが対象になるんやな。」
ぼく「プロシージャってそもそもなんやねん。」
ぼく「戻り値のない関数のことか、草属性のポケモンと名前似てるやん。」
ぼく「てことは、対象のテーブルのデータが更新されたタイミングにトリガーを設定すれば自分の更新したいテーブルに何かできるってことなんか。」
ぼく「で、ストアドプロシージャーはなんや・・・。」
ぼく「複数のSQLを一つにまとめてRDBMSに、この場合はMySQLやな?そこに保存して実行できるもの。」
ぼく「めっちゃ便利やん。」

タイミングとかある。

ぼく「データ更新のタイミングいうけど、更新する前と後があるやん・・・。それも指定できんのかな?」
ぼく「ふんふん。BEFOREとAFTERでトリガーの起動タイミングを指定するねんな。」
ぼく「それで?データ更新は起動タイミングを記載した後にINSERT,UPDATE,DELETEのどれかを指定すると・・・。」
ぼく「対象となるテーブルはどこで設定すればええんや?」
ぼく「データの更新を記載した後にON 対象となるテーブル名みたいな感じで書くねんな。」
ぼく「で、使ってるのはMySQLやからFOR EACH ROWとその後に書くと・・・。」
ぼく「なるほどね。とりあえず作ってみるか。」

DELIMITER //
CREATE TRIGGER test1
BEFORE UPDATE ON items
FOR EACH ROW
BEGIN
    CALL 呼びたいストアドプロシージャー(引数);
END
//
DELIMITER ;

ぼく「これでitemsテーブルを更新するとストアドプロシージャーが自動で実行されんのか・・・。」

トリガーの実行

ぼく「トリガーを実行するためには、自分で設定したテーブルの更新をしたらええねんな。」

UPDATE
    items  
SET 
    value=100
WHERE
    Items.id = 1;

ぼく「おお、ほんまにトリガーが起動して、自分が呼び出したいストアドプロシージャーが動いてるで!」

トリガーの削除

ぼく「削除したいときは、DROP TRIGGERに自分が作成したトリガーを指定するねんな。」

DROP TRIGGER test1;

ぼく「トリガーと結びつけたitemsテーブルが削除されたときもトリガーは、一緒に削除されんのか。」

ストアドプロシージャーとトリガーの違い

ぼく「トリガーで設定したタイミングにストアドプロシージャーを動かせるようになったで!」
ぼく「トリガーもプロシージャーやし、ストアドプロシージャーもそうやん・・・。」
ぼく「データベースで更新タイミングがあったときに実行されるストアドプロシージャーのことを・・・。」
ぼく「トリガーと呼ぶ。」
ぼく「先輩の沈黙もあながち答えとして間違ってないやん」

終わり

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む