20190403のMySQLに関する記事は4件です。

18日目:トランザクションって

授業でやった扱った『トランザクション』について、書く。

参考:「トランザクション」とは何か?を超わかりやすく語ってみた!
解説用の図が分かりやすかった。
あと、ウィキペディアも参照

トランザクションとは

コンピュータ内で実行される、分けることのできない一連の情報処理の一単位。

トランザクション処理における永続性記憶資源の管理では、複数のデータ項目の更新操作列をすべて実行するか、まったく実行しないように制御する必要がある。

ACID 標準

また、トランザクション処理システムは4つの属性の機能をサポートしており、頭文字からACID標準という。
- A : Atomic 不可分性
- C : Consistency 一貫性
- I : Isolation 独立性
- D : Durability 永続性

実際に動かしてみる

DB内で操作

参照:13.3.1 START TRANSACTION、COMMIT、および ROLLBACK 構文 -MySQLリファレンス

MySQLはデフォルトで、自動コミットモードが有効になった状態で動作し、実行するとすぐに、ディスクに格納されて永続的になります。この変更はロールバックできない。

自動コミットモードを暗黙的に無効にするには、START TRANSACTIONをし、その後、COMMITまたはROLLBACK で終了するまで、自動コミットは無効のままになります。そのあと、自動コミットモードはその以前の状態に戻ります。

実作業

今回はMySQLと、以前に作成した大学生徒データAppのデータを再利用する

terminal
mysql -u root -p
MySQL
#使用するデータベース情報の選択
mysql> USE cebu_college_development;

#使用するstudentsテーブルの構造を確認
mysql> DESC students;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | YES  |     | NULL    |                |
| email      | varchar(255) | YES  |     | NULL    |                |
| gender     | int(11)      | YES  |     | NULL    |                |
| age        | int(11)      | YES  |     | NULL    |                |
| opinion    | text         | YES  |     | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
#id3のtaro-2さんを使ってみる
mysql> SELECT name FROM students WHERE id = 3;
+--------+
| name   |
+--------+
| taro-2 |
+--------+

COMMITするパターン

#
mysql> START TRANSACTION;

# id=3のnameを"tran-sakuko"に更新
mysql> UPDATE students SET name = "tran-sakuko" WHERE id = 3;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

# 確認してみる
mysql> SELECT name FROM students WHERE id =3;
+-------------+
| name        |
+-------------+
| tran-sakuko |
+-------------+
1 row in set (0.00 sec)

# コミットする
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)

# COMMITされてるか確認
mysql> select name from students where id =3;
+-------------+
| name        |
+-------------+
| tran-sakuko |
+-------------+
1 row in set (0.00 sec)

ROLLBACKするパターン

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

# id3のnameを"tran sakutarou"に更新
mysql> UPDATE students SET name = "tran sakutarou" WHERE id =3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

# id3のnameに新しいデータの"tran sakutarou"がセットされてる
mysql> SELECT name FROM students WHERE id = 3;
+----------------+
| name           |
+----------------+
| tran sakutarou |
+----------------+
1 row in set (0.00 sec)

# ロールバックしてみる
mysql> ROLLBACK;
Query OK, 0 rows affected (0.01 sec)

#セットした"trans sakutarou"というデータはDBに格納されない。
mysql> SELECT name FROM students WHERE id = 3;
+-------------+
| name        |
+-------------+
| tran-sakuko |
+-------------+
1 row in set (0.00 sec)

RubyonRailsで動かしてみる

新しく、アプリを作成する。
今回はDBの操作だけなので、rails g modelコマンドのみ使用
テーブルはUserとReviewの2つ。

準備

terminal
rails new transact_self -d mysql
# database.ymlを編集後
rails db:create
# Userモデル作成
rails g model User name:string approved:boolean deleted:boolean
# Reviewモデル作成
rails g model Review user:references rate:integer approved:boolean
rails db:migrate

データ入力

console
(1..5).each do |i|
  User.create(name: "taro-#{i}", approved: true, deleted: false)
end
console
(1..5).each do |i|
  user = User.first
  Review.create!(user_id: user.id, rate: i, approved: true)
end

DB内で確認

mysql
mysql> SELECT * FROM users;
+----+--------+----------+---------+---------------------+---------------------+
| id | name   | approved | deleted | created_at          | updated_at          |
+----+--------+----------+---------+---------------------+---------------------+
|  1 | taro-1 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
|  2 | taro-2 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
|  3 | taro-3 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
|  4 | taro-4 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
|  5 | taro-5 |        1 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:01:31 |
+----+--------+----------+---------+---------------------+---------------------+

mysql> SELECT * FROM reviews;
+----+---------+------+----------+---------------------+---------------------+
| id | user_id | rate | approved | created_at          | updated_at          |
+----+---------+------+----------+---------------------+---------------------+
|  1 |       1 |    1 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
|  2 |       1 |    2 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
|  3 |       1 |    3 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
|  4 |       1 |    4 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
|  5 |       1 |    5 |        1 | 2019-04-03 09:01:38 | 2019-04-03 09:01:38 |
+----+---------+------+----------+---------------------+---------------------+

userとreviewとの、関連付け

app/models/user.rb
class User < ApplicationRecord
  has_many :reviews
end

reviewモデルにvalidation追加
approvedカラムを空欄不可にしておく。

app/models/review.rb
class Review < ApplicationRecord
  belongs_to :user
  validates :approved, presence: true
end

コンソールで、トランザクション処理の挙動を確認

1. トランザクション処理に成功し、commitされるパターン

console
user = User.first
User.transaction do
  user.update!(approved: false)
  user.reviews.each { |review| review.update!(approved: true) }
end
=>(0.1ms)  BEGIN
  User Update (0.3ms)  UPDATE `users` SET `approved` = FALSE, `updated_at` = '2019-04-03 09:10:46' WHERE `users`.`id` = 1
  Review Load (0.2ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1
   (6.8ms)  COMMIT
=> [#<Review id: 1, user_id: 1, rate: 1, approved: true, created_at: "2019-04-03 09:01:38", updated_at: "2019-04-03 09:01:38">, 
#<Review id: 2, user_id: 1, rate: 2,approved: true, created_at: "2019-04-03 09:01:38", updated_at: "2019-04-03 09:01:38">, 
#<Review id: 3, user_id: 1, rate: 3, approved: true, created_at: "2019-04-03 09:01:38", updated_at: "2019-04-03 09:01:38">,
,,,

userテーブルのid1のtaro-1が、更新されてる

mysql
mysql> select * from users;
+----+--------+----------+---------+---------------------+---------------------+
| id | name   | approved | deleted | created_at          | updated_at          |
+----+--------+----------+---------+---------------------+---------------------+
|  1 | taro-1 |        0 |       0 | 2019-04-03 09:01:31 | 2019-04-03 09:10:46 |
  1. トランザクション処理に失敗し、rollbackされるパターン :console user = User.first User.transaction do user.update!(approved: true) user.reviews.each { |review| review.update!(approved: false) } end =>(0.1ms) BEGIN User Update (0.2ms) UPDATE `users` SET `approved` = TRUE, `updated_at` = '2019-04-03 09:14:44' WHERE `users`.`id` = 1 Review Load (0.2ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1 (6.6ms) ROLLBACK Traceback (most recent call last): 3: from (irb):2 2: from (irb):4:in `block in irb_binding' 1: from (irb):4:in `block (2 levels) in irb_binding' ActiveRecord::RecordInvalid (Validation failed: Approved can't be blank) トランザクション処理に失敗し、rollbackしたため、DBに変化はない。

modelファイルを編集して実装

app/models/user.rb
class User < ApplicationRecord
  has_many :reviews

  def suspend!
    self.class.transaction do
      disapprove_user!
      disapprove_reviews!
    end
  end                                                                                                                                                              

private
  def disapprove_user!
    self.update!(approved: false)
  end

  def disapprove_reviews!
    reviews.each { |review| review.update!(approved: false) }
  end
end

と、ここまでが授業でやった分。
トランザクション処理の概念は理解できた(と思う)。

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

ことりんと一緒 Springもね - 10. データベースマイグレーション - Flyway for MySQL

概要 / 説明

ことりんと一緒 Springもね - 9. データベースマイグレーション - Flyway では、Flywayを利用したデータベースレイアウトのバージョン管理の方法を確認しました。
そこで利用したデータベースは、作業の簡略化のために H2 Database を使用し、アプリケーションの起動とともに利用できるインメモリデータベースの状態で実施しました。

一方で実際にアプリケーションを稼働させる場合は、インメモリデータベースではなく、データベースサーバを立ち上げてドライバ経由で接続するケースがほとんどだと思います。
そこで、今回は MySQL をデータベースサーバとして起動し、それに対して Flyway を使用してみます。

flyway-logo-tm.png

前提 / 環境

ランタイムバージョン

  • Kotlin : 1.3.21
  • SpringBoot : 2.1.1.RELEASE

Spring Dependencies

  • Web
  • JDBC
  • JPA
  • Actuator
  • Flyway

開発環境

  • OS : Mac
  • IDE : IntelliJ IDEA
  • Build : Gradle

手順 / 解説

MySQL サーバの準備

MySQLサーバは Docker コンテナを利用して簡単に用意します、

docker-compose.yml の用意

次の環境が起動するようなd ocker-compose.yml を作成します。

項目 内容
MySQL バージョン 5.7
アクセスユーザ guest
アクセスパスワード guest
version: "3.7"
services:
  db:
    image: mysql:5.7
    container_name: my_db
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: guest
      MYSQL_PASSWORD: guest
      MYSQL_DATABASE: guest
    restart: always
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - 3306:3306

MySQL サーバの起動

docker-compose.yml の配置場所、またはファイルを -f オプションで指定して、次のコマンドで Docker コンテナとして MySQL を起動します。

$ docker-compose up -d

Application 定義

以下のデータベース接続定義及びFlyway定義を追加します。

項目 設定値
JDBCドライバ com.mysql.cj.jdbc.Driver
データベース接続URL jdbc:mysql://localhost:3306/app?autoReconnect=true&useSSL=false
アクセスユーザ guest
アクセスパスワード guest
Flyway ベースラインバージョン 0.0.0
Flyway ベースライン説明 << Flyway Baseline >>
Flyway マイグレーション・スクリプトの配置場所 classpath:db/migration
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/app?autoReconnect=true&useSSL=false
    username: guest
    password: guest
  flyway:
    enabled: true
    url: jdbc:mysql://localhost:3306/app?autoReconnect=true&useSSL=false
    user: guest
    password: guest
    baseline-on-migrate: true
    baseline-version: 0.0.0
    baseline-description: << Flyway Baseline >>
    locations: classpath:db/migration

Flyway実行 (アプリケーション起動)

アプリケーションの起動時にFlywayを実行します。

SpringBoot 実行

以下のコマンドで SpringBoot を実行します。

$ ./gradlew bootRun

起動時ログから Flyway が正常実行されている事が確認できます。

[           main] o.f.c.internal.license.VersionPrinter    : Flyway Community Edition 5.2.4 by Boxfuse
[           main] o.f.c.internal.database.DatabaseFactory  : Database: jdbc:mysql://localhost:3306/app (MySQL 5.7)
[           main] o.f.core.internal.command.DbValidate     : Successfully validated 2 migrations (execution time 00:00.019s)
[           main] o.f.c.i.s.JdbcTableSchemaHistory         : Creating Schema History table: `app`.`flyway_schema_history`
[           main] o.f.core.internal.command.DbMigrate      : Current version of schema `app`: << Empty Schema >>
[           main] o.f.core.internal.command.DbMigrate      : Migrating schema `app` to version 1.0.0 - Create-InitialTable
[           main] o.f.core.internal.command.DbMigrate      : Migrating schema `app` to version 1.1.0 - Insert-InitialData
[           main] o.f.core.internal.command.DbMigrate      : Successfully applied 2 migrations to schema `app` (execution time 00:00.109s)

MySQL 確認

MySQLにアクセスしFlywayによる変更を確認します。

mysql> show tables;
+-----------------------+
| Tables_in_app         |
+-----------------------+
| flyway_schema_history |
| message               |
+-----------------------+
mysql> select id,title,message from message;
+--------------------------------------+-------+--------------------+
| id                                   | title | message            |
+--------------------------------------+-------+--------------------+
| 12345678-e9d9-4d1e-ba79-01f8b8715ba9 | INIT  | Inserted by FLYWAY |
| 7b23257c-e9d9-4d1e-ba79-01f8b8715ba9 | INIT  | Inserted by FLYWAY |
+--------------------------------------+-------+--------------------+
2 rows in set (0.00 sec)

message テーブルと、Flyway の履歴管理テーブル flyway_schema_history が作成されている事が確認でき、
データが追加されている事も確認できました。

まとめ / 振り返り

組み込みモードでもサーバモードでも起動しているデータベースに対してアクセスする事ができれば、Flywayが使用できる事が確認できました。
データベースレイアウトに変更が発生しがちな開発中のタイミングでは非常に便利なツールだと思います。

今回のソース

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

DockerでWordPress環境を構築したらしくじった。でも頑張ったら上手く行ったので情報共有します。

初投稿です。覚書のようなものなので、ざーっとラフに記載します。

こちらの記事を参考にさせていただきつつ、Docker上でWordPressのローカル環境を構築してみたものの、自分の環境ではうまく動きませんでした。環境は下記。

  • MacBookPro2018 + MacOSX Mojave(10.14.3)
  • DockerDesktop 2.0.0.3(31259)

ローカルWEBサーバー(WordPress側)、ローカルDBサーバー(MySQL側)共に動作しているのですが、WordPress側がMySQL側を参照できていないようで Error establishing a database connection が表示されました。

原因を調べたところ、MySQLのバージョンがVer8系だったことに起因していました。バージョン未指定でMySQLのImageをDocker Hubからpullすると、latest(Ver8系最新版)がダウンロードされます。しかしWordPressはそのままではMySQL Ver8系では正常動作しないようです。

ググってみたところ、MySQL Ver8系でWordPressを正常動作させる方法は色々あるようですが、一旦動作するところまで持っていきたかったため、 docker run コマンドでコンテナを走らせる際、Ver5.7系のImageを使うよう、下記の指定をしたところ無事に動作しました。

docker pull mysql:5.7.25
docker pull wordpress:4.9.1

docker run --name mysql5.7.25 -e MYSQL_ROOT_PASSWORD=password -d mysql:5.7.25
docker run --name wordpress4.9.1 -v /Users/dee909/dev/docker:/tmp --link mysql5.7.25:mysql -d -p 8080:80 wordpress:4.9.1

動作検証は、WordPress Ver4.9.1、MySQL Ver5.7.25で行いました。
もし、私と同様に行き詰まった際はお試しください。
セキュリティ等の観点からWordPress、MySQL共に最新版が望ましいので、自力解決できる方はチャレンジいただければと思います。
そしてヒントを下さった、@pugiemonnさん、@maimaxさん、ありがとうございました。

追記1

コンテナを走らせる際に、MySQL側にポートフォワーディングを追加してやると、Sequel Pro等からもDBへのアクセスが可能になります。下記はポート3307を3306にフォワードさせています。GUI操作がお好みの方は、一緒に設定してしまうと良いと思います。

docker run --name mysql5.7.25 -e MYSQL_ROOT_PASSWORD=password -d -p 3307:3306 mysql:5.7.25

スクリーンショット 2019-04-03 20.58.45.png

追記2

WordPress側の -v オプションはホストとコンテナ内のディレクトリ共有のための紐付けですので、ご自身の環境に合わせて変更してください。共有が不要ならば、まるっと削除しても問題ないです。
サンプルの docker run コマンドではホスト上の /Users/dee909/dev/docker ディレクトリをコンテナ内の /tmp ディレクトリに紐付けしていますが、 -v /Users/dee909/dev/docker:/var/www/html と変更することで、ホスト側(MacOSXやWindows上)に設置したWordPressをコンテナのWEBサーバー上で動作させることができます。
こちらの方がホスト側のテキストエディタでコード編集が行えますし、ホスト上のディレクトリをgit管理することもできますので、使い勝手が良いと思います。

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

RDS for MySQL のエンコーディングやコレーションの変更を既存の DB インスタンスに反映させる

はじめに

Rails アプリケーションから扱っていた、数年間運用し続けている Amazon RDS for MySQL に新たなテーブルを作っていたところ、テーブルのデフォルトのエンコーディングが utf8 になっているのを発見しました。
これでは絵文字を格納することが出来ません! 困りましたね、エンコーディングを utf8mb4 に変更しましょう! コレーションも utf8mb4_general_ci に変更してしまいましょう!

といったシチュエーションで、MySQL インスタンスに設定しているパラメータグループの値を変更しても何やら変更が反映されない現象に遭遇したので、これを記します。

環境

  • Aurora for MySQL 5.7.12
    • 恐らく 5.6系の MySQL や Aurora for MySQL にも共通するはず

結論先取

公式ドキュメントのDB パラメータグループを使用するをよく読みましょう。

DB インスタンスの文字セットまたは照合パラメータを変更した場合、パラメータの変更は既存のデータベースに適用されません。

と書かれている通り、エンコーディングやコレーションのパラメータの変更は既存のデータベースには適用されず、新規のDBインスタンスの作成時にしかエンコーディングに関するパラメータグループの設定は反映されません。

既存のデータベースのエンコーディングを変更したい場合は、ドキュメントにもある通り

ALTER DATABASE database_name CHARACTER SET character_set_name COLLATE collation;

を実行する必要があります。

素直にパラメータグループを変更する

スナップショットから検証環境を用意して、素直にパラメータグループを変更して実験をしてみましょう。
私達の場合は、セッションを張るクライアント側でエンコーディングを utf8mb4 に明示的に指定していたので character_set_database の値を utf8mb4 に変更するだけで良さそうでした(実際に私達が運用していたのは MySQL ではなく Aurora for MySQL だったので、 character_set_database はパラメーターグループではなくクラスターパラメーターグループの中に存在していました)。
AWS のマネジメントコンソールから変更してみましょう(本当は Terraform などで記述するべきです!)。

RDS · AWS Console 2019-04-03 00-37-14.jpg

どうやら再起動が必要という声をよく耳にするので、再起動してからデータベースに接続してみましょう。:v_tone3:

SHOW GLOBAL VARIABLES LIKE `chara%`;

(MySQL 5.7.16) qiita-dev:qiita 2019-04-03 00-43-54.jpg

おや、反映されていなさそうです。それに utf8 ではなく latin1 です。参りました。
character_set_database は Dynamic パラメータとのことなので、再起動せずともインスタンスを起動したまま変更を反映する事が可能なはずですが、念を入れて再起動をしてもなお反映されないのは随分不可解です。

公式ドキュメントのDB パラメータグループを使用するをよく読み返してみましょう:rolling_eyes:

DB インスタンスの文字セットまたは照合パラメータを変更した場合、パラメータの変更は既存のデータベースに適用されません。

:joy::joy::joy:

Dynamic パラメータとは一体という気持ちになってきましたね :innocent:

どうやらエンコーディングやコレーションに関するパラメータの設定が反映されるタイミングは新規の DB インスタンスを立ち上げる瞬間のみであり、既存の DB インスタンスに対して設定が反映されることはないようです!
検証まではしていませんが、私達が運用していた DB インスタンスは幾度のクローンやリストアを繰り返していたものなので、本当に新規でインスタンスを立ち上げる瞬間のみが反映タイミングであるように思えます。

ALTER DATABASE を実行する

私達が DB インスタンスを新たに立ち上げる事はしばらく無いように思えたので、公式ドキュメントの案内通りに直接データベースのエンコーディングとコレーションを変更する SQL を実行する他にはなさそうです!

ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

データベースのエンコーディングとコレーションを変更した場合、影響範囲は以下のようになります。

  • 既存のテーブル、カラム
    • 過去に設定されたエンコーディング、コレーションが維持される
  • 新規に作成するテーブル、カラム
    • 今回設定したエンコーディング、コレーションが反映される

デフォルトのエンコーディングとコレーションを変更するだけで、既存のテーブルのエンコーディングとコレーション(私達の場合は utf8)は変更されず、INDEX の再構築などは行われません。
つまり、ダウンタイム無しで設定を変更できるということになりますね!:joy:

もちろん、既存のテーブルやカラムの変更が行われない為、これらのエンコーディングとコレーションを変更したい場合は INDEX 再構築等を伴う操作が必要になってくるので、それは必要に迫られた時に対応を考える必要があります。

少なくとも、私達が目指していた「新しく作られるテーブルのエンコーディングが utf8mb4 にならない」という問題は、これで解決出来たようです! やりましたね!

ちなみに、既存のテーブル、カラムのエンコーディング・コレーションを一発で変更する SQL は以下の通りです。

ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4;

テーブル内のカラムも自動的に変換してくれるので便利ですね :smile:

本当はどうすべきだったの?

先人が再三口酸っぱく言っているように、DB インスタンスを立ち上げる前にちゃんとパラメータグループの設定を見直すべきです。

そうすれば、パラメータグループから MySQL のグローバル設定や各種テーブルの設定まで一気通貫で整合性が取れる状態になり、誰も戸惑わずに済みました。

先人から学ぶことは本当に多いですね! :smile:

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