- 投稿日:2019-04-03T21:04:15+09:00
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のデータを再利用する
terminalmysql -u root -pMySQL#使用するデータベース情報の選択 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つ。準備
terminalrails 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) endconsole(1..5).each do |i| user = User.first Review.create!(user_id: user.id, rate: i, approved: true) endDB内で確認
mysqlmysql> 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.rbclass User < ApplicationRecord has_many :reviews endreviewモデルにvalidation追加
approvedカラムを空欄不可にしておく。app/models/review.rbclass Review < ApplicationRecord belongs_to :user validates :approved, presence: true endコンソールで、トランザクション処理の挙動を確認
1. トランザクション処理に成功し、commitされるパターン
consoleuser = 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が、更新されてる
mysqlmysql> 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 |
- トランザクション処理に失敗し、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.rbclass 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と、ここまでが授業でやった分。
トランザクション処理の概念は理解できた(と思う)。
- 投稿日:2019-04-03T20:18:21+09:00
ことりんと一緒 Springもね - 10. データベースマイグレーション - Flyway for MySQL
概要 / 説明
ことりんと一緒 Springもね - 9. データベースマイグレーション - Flyway では、Flywayを利用したデータベースレイアウトのバージョン管理の方法を確認しました。
そこで利用したデータベースは、作業の簡略化のために H2 Database を使用し、アプリケーションの起動とともに利用できるインメモリデータベースの状態で実施しました。一方で実際にアプリケーションを稼働させる場合は、インメモリデータベースではなく、データベースサーバを立ち上げてドライバ経由で接続するケースがほとんどだと思います。
そこで、今回は MySQL をデータベースサーバとして起動し、それに対して Flyway を使用してみます。前提 / 環境
ランタイムバージョン
- 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:3306MySQL サーバの起動
docker-compose.yml
の配置場所、またはファイルを-f
オプションで指定して、次のコマンドで Docker コンテナとして MySQL を起動します。$ docker-compose up -dApplication 定義
以下のデータベース接続定義及び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/migrationFlyway実行 (アプリケーション起動)
アプリケーションの起動時に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が使用できる事が確認できました。
データベースレイアウトに変更が発生しがちな開発中のタイミングでは非常に便利なツールだと思います。今回のソース
- 投稿日:2019-04-03T18:21:55+09:00
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追記2
WordPress側の
-v
オプションはホストとコンテナ内のディレクトリ共有のための紐付けですので、ご自身の環境に合わせて変更してください。共有が不要ならば、まるっと削除しても問題ないです。
サンプルのdocker run
コマンドではホスト上の/Users/dee909/dev/docker
ディレクトリをコンテナ内の/tmp
ディレクトリに紐付けしていますが、-v /Users/dee909/dev/docker:/var/www/html
と変更することで、ホスト側(MacOSXやWindows上)に設置したWordPressをコンテナのWEBサーバー上で動作させることができます。
こちらの方がホスト側のテキストエディタでコード編集が行えますし、ホスト上のディレクトリをgit管理することもできますので、使い勝手が良いと思います。
- 投稿日:2019-04-03T00:54:55+09:00
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 などで記述するべきです!)。どうやら再起動が必要という声をよく耳にするので、再起動してからデータベースに接続してみましょう。
SHOW GLOBAL VARIABLES LIKE `chara%`;おや、反映されていなさそうです。それに
utf8
ではなくlatin1
です。参りました。
character_set_database
は Dynamic パラメータとのことなので、再起動せずともインスタンスを起動したまま変更を反映する事が可能なはずですが、念を入れて再起動をしてもなお反映されないのは随分不可解です。公式ドキュメントのDB パラメータグループを使用するをよく読み返してみましょう
DB インスタンスの文字セットまたは照合パラメータを変更した場合、パラメータの変更は既存のデータベースに適用されません。
Dynamic パラメータとは一体という気持ちになってきましたね
どうやらエンコーディングやコレーションに関するパラメータの設定が反映されるタイミングは新規の DB インスタンスを立ち上げる瞬間のみであり、既存の DB インスタンスに対して設定が反映されることはないようです!
検証まではしていませんが、私達が運用していた DB インスタンスは幾度のクローンやリストアを繰り返していたものなので、本当に新規でインスタンスを立ち上げる瞬間のみが反映タイミングであるように思えます。ALTER DATABASE を実行する
私達が DB インスタンスを新たに立ち上げる事はしばらく無いように思えたので、公式ドキュメントの案内通りに直接データベースのエンコーディングとコレーションを変更する SQL を実行する他にはなさそうです!
ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;データベースのエンコーディングとコレーションを変更した場合、影響範囲は以下のようになります。
- 既存のテーブル、カラム
- 過去に設定されたエンコーディング、コレーションが維持される
- 新規に作成するテーブル、カラム
- 今回設定したエンコーディング、コレーションが反映される
デフォルトのエンコーディングとコレーションを変更するだけで、既存のテーブルのエンコーディングとコレーション(私達の場合は
utf8
)は変更されず、INDEX の再構築などは行われません。
つまり、ダウンタイム無しで設定を変更できるということになりますね!もちろん、既存のテーブルやカラムの変更が行われない為、これらのエンコーディングとコレーションを変更したい場合は INDEX 再構築等を伴う操作が必要になってくるので、それは必要に迫られた時に対応を考える必要があります。
少なくとも、私達が目指していた「新しく作られるテーブルのエンコーディングが
utf8mb4
にならない」という問題は、これで解決出来たようです! やりましたね!ちなみに、既存のテーブル、カラムのエンコーディング・コレーションを一発で変更する SQL は以下の通りです。
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4;テーブル内のカラムも自動的に変換してくれるので便利ですね
本当はどうすべきだったの?
先人が再三口酸っぱく言っているように、DB インスタンスを立ち上げる前にちゃんとパラメータグループの設定を見直すべきです。
そうすれば、パラメータグループから MySQL のグローバル設定や各種テーブルの設定まで一気通貫で整合性が取れる状態になり、誰も戸惑わずに済みました。
先人から学ぶことは本当に多いですね!