20190405のMySQLに関する記事は9件です。

MacOSにGraphQL環境を爆速で用意する方法

Prisamを使ってGraphQL環境をサクッと作って見ましょう。
今回データストアはMySQLを使います。

前提

上記はすでにインストールされている前提で進めます。

1.Prismaのインストール

$ npm install -g prisma

2. Prismaプロジェクトの作成

$ prisma init {プロジェクト名}

今回は test-app というプロジェクト名で進めます。

実行すると対話式で進みます。

$ prisma init test-app

? Set up a new Prisma server or deploy to an existing server? (Use arrow keys)

  Set up a new Prisma server for local development (based on docker-compose):
  Use existing database      Connect to existing database
❯ Create new database        Set up a local database using Docker

  Or deploy to an existing Prisma server:
  Demo server                Hosted demo environment incl. database (requires login)
  Use other server           Manually provide endpoint of a running Prisma server

Create new database を選んで新しくデータベースを作成しましょう。

? What kind of database do you want to deploy to? (Use arrow keys)
❯ MySQL             MySQL compliant databases like MySQL or MariaDB
  PostgreSQL        PostgreSQL database
  MongoDB           Mongo Database

MySQLを選択します。

? Select the programming language for the generated Prisma client (Use arrow keys)
❯ Prisma TypeScript Client
  Prisma Flow Client
  Prisma JavaScript Client
  Prisma Go Client
  Don't generate

使用したいものを選んでください。
今回は Prisma TypeScript Client を使用します。

Next steps:

  1. Open folder: cd test-app
  2. Start your Prisma server: docker-compose up -d
  3. Deploy your Prisma service: prisma deploy
  4. Read more about Prisma server:
     http://bit.ly/prisma-server-overview

Generating schema... 16ms
Saving Prisma Client (TypeScript) at

プロジェクトの作成はこれでOKです。

3. Dockerのセットアップ

$ cd test-app

プロジェクトディレクトリに入って

$ docker-compose up -d
Creating network "test-app_default" with the default driver
Creating volume "test-app_mysql" with default driver
Pulling prisma (prismagraphql/prisma:1.30)...
1.30: Pulling from prismagraphql/prisma
.
.
.

Dockerのセットアップが始まります。

Creating test-app_mysql_1  ... done
Creating test-app_prisma_1 ... done

これでDockerのセットアップ完了です。

Prismaデプロイ

$ prisma deploy
Creating stage default for service default ✔
Deploying service `default` to stage `default` to server `local` 557ms

Changes:

  User (Type)
  + Created type `User`
  + Created field `id` of type `ID!`
  + Created field `name` of type `String!`
  + Created field `updatedAt` of type `DateTime!`
  + Created field `createdAt` of type `DateTime!`

Applying changes 1.1s

Your Prisma GraphQL database endpoint is live:

  HTTP:  http://localhost:4466
  WS:    ws://localhost:4466

You can view & edit your data here:

  Prisma Admin: http://localhost:4466/_admin

スキーマなどの作成が行われて管理画面にアクセスできるようになります。

スクリーンショット 2019-04-05 11.38.48.png

まだデータベースにデータが入っていないため、空っぽです。

MySQLにデータを追加する

Dockerを直接操作してもいいですが、今回はクライアントアプリを使って操作します。

クライアントアプリからDockeへ接続するために設定ファイルを書き換えます。

docker-compose.yml
mysql:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: prisma
    volumes:
      - mysql:/var/lib/mysql
+   ports:
+     - "4467:3306"

設定を反映するため、Dockerの再起動を行います。

$ docker-compose stop

※ restart もあるが、何故か反映されないためstopする

$ docker-compose up -d

完了した後、Dockerステートを確認

$ docker ps

Dockerのステートを確認する

CONTAINER ID        IMAGE                       COMMAND                  CREATED              STATUS              PORTS                               NAMES
532cfa5d7aff        mysql:5.7                   "docker-entrypoint.s…"   About a minute ago   Up About a minute   33060/tcp, 0.0.0.0:4467->3306/tcp   hello-world_mysql_1
57133a0b6409        prismagraphql/prisma:1.30   "/bin/sh -c /app/sta…"   16 hours ago         Up About a minute   0.0.0.0:4466->4466/tcp              hello-world_prisma_1

mysql:5.7 の項目の PORTS 0.0.0.0:4467->3306になっていればOK

クライアントアプリからログインする

今回は Sequel Proを使います

スクリーンショット 2019-04-05 21.58.03.png

スクリーンショット 2019-04-05 22.03.49.png

default@default に入ると、 User テーブルが存在するので
試しにデータを入れてみましょう。

スクリーンショット 2019-04-05 22.05.56.png

では、反映されているか確認するため、管理画面でクエリを叩いてみましょう。

スクリーンショット 2019-04-05 22.07.13.png

反映されました!

[おまけ]カラムを追加する

Userage を追加してみます。

datamodel.prisma
type User {
  id: ID! @unique
  name: String!
+ age: Int!
}

スクリーンショット 2019-04-05 22.12.13.png

prisma deploy

変更をデプロイします

Deploying service `default` to stage `default` to server `local` 272ms

Changes:

  User (Type)
  + Created field `age` of type `Int`

Applying changes 1.1s

Your Prisma GraphQL database endpoint is live:

  HTTP:  http://localhost:4466
  WS:    ws://localhost:4466

You can view & edit your data here:

  Prisma Admin: http://localhost:4466/_admin

変更が検知されました!

管理画面をリロードして確認しましょう。

スクリーンショット 2019-04-05 22.15.56.png

これで、カラムの追加分が反映されました!

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

Google Cloud Platform上でNode.jsとMySQLを繋げてみた

はじめに

Google Cloud Platform (GCP) には魅力的な無料枠があり、操作も比較的簡単そうだったのでGCP上に構築したNode.js+MySQLのスタックを接続してみました。

今回はGCPのApp EngineにNode.jsのアプリを構築し、Cloud SQL第二世代にMySQLを構築させました。

https://cloud.google.com/sql/docs/mysql/connect-app-engine?hl=ja
接続についてはこのリファレンスを読みましたがNode.jsが載っていなかったので書き残します。

料金について

App Engineは規模が小さい場合基本的に無料ですが、Cloud SQLの運用は課金されました。
無料枠について:https://cloud.google.com/free/?hl=ja 
Cloud SQL料金について:https://cloud.google.com/sql/pricing?hl=ja

Instanceの接続名の確認

Instanceの接続名はCloud SQLの接続に必要な項目です。project_id:region:instance_idの形式の文字列です。
GCPの「インスタンスの詳細」画面の中の「このインスタンスに接続」というボックスの中に表示されています。
これをコピーして、環境変数として保存しておきます。

.env
# プロジェクト名:hoge、インスタンス名:hoge-db、地域:東京 の場合
INSTANCE_CONNECTION_NAME="hoge:asia-northeast1:hoge-db"

MySQL関係の項目の確認

Node.jsからMySQLにアクセスするにはもちろんデータベース情報が必要なので、これも一緒に環境変数として保存しておきます。

.env
# ...
DB_USER="hogehoge"
DB_PASSWORD="fugafuga"
DB_DATABASE="piyopiyo"

Node.js側の設定

Node.jsからMySQLに接続するモジュールは色々ありますが、mysqlというクライエントを使いました。

App Engineのスタンダード環境からCloud SQLのDatabaseに接続するときには、普通にTCPでは接続できないため、Unix Domain Socketという方法で接続します。

環境変数はdotenvというパッケージを使って取り込みました。

require('dotenv').config();

const mysql = require('mysql');

const connection = mysql.createConnection({
  socketPath: `/cloudsql/${process.env.INSTANCE_CONNECTION_NAME}`,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE 
});

App Engineスタンダード環境の場合、/cloudsql/<インスタンス接続名>のソケットが自動的に提供されるようなのでこのほかに何も設定する必要はありません。

接続プール

パフォーマンスを上げるためにプールを使う場合も、同じように接続できます。

const pool = mysql.createPool({
  user: process.env.DB_USER, 
  password: process.env.DB_PASS, 
  database: process.env.DB_NAME, 
  socketPath: `/cloudsql/${process.env.INSTANCE_CONNECTION_NAME}`
});

接続数上限

プールを使用する場合、接続数の上限も設定できます。

const pool = mysql.createPool({
  // ...
  connectionLimit: 5
});

TCPとの併用

開発環境ではローカルのMySQLにTCPで接続してほしかったので、NODE_ENVの環境変数に合わせて接続方法を変えるようにしました。

const pool = process.env.NODE_ENV === 'production'
  ? mysql.createPool({
    user: process.env.DB_USER, 
    password: process.env.DB_PASS, 
    database: process.env.DB_NAME, 
    socketPath: `/cloudsql/${process.env.INSTANCE_CONNECTION_NAME}`
  }) : mysql.createPool({
    user: process.env.DB_USER, 
    password: process.env.DB_PASS, 
    database: process.env.DB_NAME, 
    host: 'localhost'
  });

完成

gcloud app deployを実行してアクセスすると、データベースに接続できているはずです。

App EngineとCloud SQLのインスタンスが別々のプロジェクトの下に作られている場合、IAMを変更する必要があります。
App EngineのサービスアカウントにCloud SQL Admin Cloud SQL Editor Cloud SQL Clientの役割のいずれかを与えると動くそうです。

感想

GCPは比較的安価でNode.js、MySQLの環境構築をすることができるので魅力的です。リファレンスにNode.jsがなかったので少し手こずりましたが、無事接続できました。

最初の記事なので、少し間違えていたらすみませんv_v。

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

WordPressのサイトURLをMySQLからDB操作で直接書き換える方法

直接SQL文でWordPressのDBを操作してサイトURLを書き換える方法です。
ローカルに開発環境を構築する際などで使う用です。よく忘れるので自分用メモ。

search.sql
select * from wp_options where option_name in ('home', 'siteurl');
update.sql
update wp_options set option_value = 'https://sample.com' where option_id in (1,2);

search.sqlで検索して出てきた値が現在のサイトURLです。
update.sqlでサイトURLを任意の値(例.sample.com)に更新します。

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

[Django]管理画面の一覧表示部分に別テーブルのフィールドを表示する

はじめに

「[Django]管理画面の一覧表示部分に、(表示対象のテーブルで外部キーなどでの関連もさせていない)別テーブルのフィールドを表示する」

最終的に結構単純でしたが、同じようなことをしている人が少なくて割と悩んだのでメモ。
多分ドキュメントに丁寧に目を通した人にとっては当たり前のことかも。

やりたいこと

上の画面を下のような表示にしたかったのですが、この時、DBの構成は下記のER図のようになっており、日時系の値とそれ以外の値は別DBのフィールドのため、単純にlist_displayで追加することができなかった。

[やりたい画面レイアウト]
図1.png

[ER図]
(記事用に適当に書き換えたので、ER図と下の方のコードの整合性保ってない部分もあるかも)
スクリーンショット 2019-04-05 14.14.53.png

要点

特に重要なのは下記の部分ですが、一応全貌を載せておきます。

重要部分1
    def plan_start_datetime(self, obj):
        list_got_objects = list(Datetime.objects.all().values_list('plan_start_datetime', flat=True))

        return list_got_objects

※1:取得したい情報の条件(例:「id=1」など)

重要部分2
    list_display = (
                    'plan_start_datetime',
                   )
要点1.

<テーブル名>.objects.all() の戻り値はQuerySetなので、values_list() で値を取得する必要がある。

要点2.

values_list() で取得した値をスライスして取得した「文字列」をlist_display に渡す。

以下、全貌

models.py
# coding: utf-8
from django.db import models
from django.utils import timezone
from django.core.validators import MaxValueValidator


# コードがforeign keyとして使用されるため、このクラスを先に定義している
class M_branch(models.Model):

    def __str__(self):
        return self.name

    code = models.CharField(max_length=4, primary_key=True)
    name = models.CharField(max_length=128)

# コードがforeign keyとして使用されるため、このクラスを先に定義している
class M_workplace(models.Model):

    def __str__(self):
        return self.name

    branch_code = models.ForeignKey(M_branch, on_delete=models.CASCADE)
    code = models.CharField(max_length=4)
    name = models.CharField(max_length=128)


class Report(models.Model):

    def __str__(self):
        return self.application_id

    application_id = models.CharField(max_length=32, primary_key=True)
    branch_code = models.ForeignKey(M_branch, on_delete=models.DO_NOTHING)
    workplace_code = models.ForeignKey(M_workplace, on_delete=models.DO_NOTHING)


class Datetime(models.Model):

    def __str__(self):
        return self.plan_start_datetime

    application_id = models.OneToOneField(Report, on_delete=models.CASCADE)
    plan_start_datetime = models.DateTimeField()
    plan_end_datetime = models.DateTimeField()
    actual_start_datetime = models.DateTimeField()
    actual_end_datetime = models.DateTimeField()

admin.py
from django.contrib import admin

from .models import Report, Datetime, Participant, M_branch, M_workplace


# TODO DatetimeやParticipantテーブルから必要な情報を取得して同じ表に表示する
class ReportAdmin(admin.ModelAdmin):
    fieldsets = [
        ('申請ID', {'fields': ['application_id']}),
        ('支部コード', {'fields': ['branch_code']}),
        ('職場コード', {'fields': ['workplace_code']}),
    ]
    list_display = ('application_id',
                    'branch_code',
                    'workplace_code',
                    'plan_start_datetime',
                    'plan_end_datetime',
                    'actual_start_datetime',
                    'actual_end_datetime',
                    )
    def plan_start_datetime(self, obj):
        list_got_objects = list(Datetime.objects.all().values_list('plan_start_datetime', flat=True))
        return plan_start_datetime
    plan_start_datetime.short_description = 'plan_start_datetime'

    def plan_end_datetime(self, obj):
        list_got_objects = list(Datetime.objects.all().values_list('plan_end_datetime', flat=True))
        return plan_end_datetime
    plan_end_datetime.short_description = 'plan_end_datetime'

    def actual_start_datetime(self, obj):
        list_got_objects = list(Datetime.objects.all().values_list('actual_start_datetime', flat=True))
        return actual_start_datetime
    actual_start_datetime.short_description = 'actual_start_datetime'

    def actual_end_datetime(self, obj):
        list_got_objects = list(Datetime.objects.all().values_list('actual_end_datetime', flat=True))
        return actual_end_datetime
    actual_end_datetime.short_description = 'actual_end_datetime'


class M_branchAdmin(admin.ModelAdmin):
    fieldsets = [
        ('支部コード', {'fields': ['code']}),
        ('支部名', {'fields': ['name']}),
    ]
    list_display = ('name', 'code')


class M_workplaceAdmin(admin.ModelAdmin):
    fieldsets = [
        ('支部コード', {'fields': ['branch_code']}),
        ('職場コード', {'fields': ['code']}),
        ('職場名',    {'fields': ['name']}),
    ]
    list_display = ('name', 'code', 'branch_code')

admin.site.register(Report, ReportAdmin)
admin.site.register(M_branch, M_branchAdmin)
admin.site.register(M_workplace, M_workplaceAdmin)

以上。

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

Heroku(PHP+MySQL)でWebサービス公開手順

はじめに

localhostでPHP+MySQLで開発したものを、Herokuで公開するためのもの
OS: mac
PHP: フレームワーク無し
DB: MySQL

1.Herokuに登録

https://www.heroku.com/
クレジットカードを登録しておきます。

2.Heroku CLIをインストール

https://devcenter.heroku.com/articles/heroku-cli

$ heroku login

3.アプリ作成

ここではデスクトップにheroku-appというディレクトリを作ります。

$ cd Desktop
$ mkdir heroku-app
$ cd heroku-app

アプリを作成します。アプリ名を指定しない場合は自動で割り振られます。

$ heroku create アプリ名

4.composer

composer.jsonというファイルを作成します。

touch composer.json

phpのバージョン7を指定します。これが正しいのか分かりませんがこれで動きました。
"ext-mbstring": "*" とすることでmb_xxxの関数(mb_strlenとか)が使えるようになります。指定しないでmb_xxxの関数を使うとCall to undefined functionエラーになります。

composer.json
{
    "require": {
      "php": "~7.0",
      "ext-mbstring": "*"
    }
 }

デフォルトのPHPが入っていることが前提です。

$ curl -sS https://getcomposer.org/installer | php
$ mv composer.phar /usr/local/bin/composer

ターミナルを再起動して確認します。大丈夫ならインストールします。

$ cd Desktop/heroku-app
$ composer -v
$ composer install

5.Gitリポジトリ作成

$ git init
$ git remote add heroku https://git.heroku.com/アプリ名.git
$ git remote -v

6.ClearDBアドオンをインストール

herokuでmysqlを使うための設定です。

$ heroku addons:add cleardb:ignite

7.Workbenchをインストール、設定

Workbenchを使うことで、phpMyAdminのようにGUIでデータベースを管理できます。
インストールが完了したら赤枠で囲ったMySQL Connectionsの横の+をクリックして設定画面を開きます。
スクリーンショット 2019-04-05 18.18.34.png

heroku configコマンドでCLEARDB_DATABASE_URLを確認します。

$ heroku config

これを元に入力していきます。CLEARDB_DATABASE_URL: mysql://ユーザ名:パスワード@サーバ名/データベース?reconnect=true
Connection Name: 適当な名前
Hostname: サーバ名
Username: ユーザ名
Password: パスワード
Default Schema: データベース

入力を終えたら、Test Connectionをクリック→上で入力したパスワードを入力→Connection Warning画面が出る→Continue AnywaySuccessfully made the MySQL connectionOKOK

8.phpMyAdminからDBエクスポート→WorkbenchにDBインポート

phpMyAdminからエクスポート

phpMyAdminからエクスポートしたいDBを選択→詳細にチェック→出力をファイルに保存する実行

Workbenchにインポート

7.で設定したものに接続してAdministraionタブからData Import/RestoreImport from Self-Contained Fileにチェックを入れてphpMyAdminからエクスポートしたsqlファイルを選択します。
Default Target Schemaを指定します。newの方ではなくてセレクトメニューでデフォルトのものを指定します。
なぜかフリーズしたように?選択できないときがありますが、謎です。
最後にStart Importをクリックします。

9.PHPのDB接続設定を変更する

local用から本番用に書き換える必要があります。

function.php
function dbConnect(){
  $db = parse_url($_SERVER['CLEARDB_DATABASE_URL']);
  $db['dbname'] = ltrim($db['path'], '/');
  $dsn = "mysql:host={$db['host']};dbname={$db['dbname']};charset=utf8";
  $user = $db['user'];
  $password = $db['pass'];
  $options = array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY =>true,
  );
  $dbh = new PDO($dsn,$user,$password,$options);
  return $dbh;
}

10.herokuへデプロイ

$ git add -A
$ git commit -m "commit"
$ git push heroku master

heroku openでブラウザを開いて表示してくれます。localと同じようにDBのデータ等ちゃんと表示できていれば完了です。

$ heroku open

11.WorkbenchでDBの中身を確認する

赤枠で囲ったSchemasTables
スクリーンショット 2019-04-06 7.52.37.png
各テーブルにマウスホバーすると、右にアイコンが出る(赤枠で囲った所)ので一番右のカレンダーみたいなアイコンをクリックするとデータが確認できます。または、SQL文を打ち込んで雷アイコンを押してもデータ確認ができます。
スクリーンショット 2019-04-06 8.15.19.png

その他エラーなど

・herokuはphpログファイルとかエラーが見れないようなので、見るにはini_set('display_errors',1)を一時的に使います。

Error Code: 2013. Lost connection to MySQL server〜というエラーが出る場合
https://stackoverflow.com/questions/10563619/error-code-2013-lost-connection-to-mysql-server-during-query

・composer.jsonを編集したら作業ディレクトリで composer update を行う

$ composer update

参考:

herokuでPHPからMySQL(ClearDB)を使うhttp://blog.a-way-out.net/blog/2014/12/11/heroku-php-mysql/
herokuがcomposer.lock必須になったのでcomposerの入れ方をメモしておくhttp://kayakuguri.github.io/blog/2015/08/25/composer-lock-require/
Heroku データベースの管理(ClearDB アドオンによる MySQL の利用)http://www.ownway.info/Ruby/heroku/how/management/database/cleardb
MySQL WorkbenchをMacにインストールする方法https://programmingnavi.com/77/
heroku環境でmb_xxx関数を使うと、Fatal error: Call to undefined function …のエラーが表示されるhttps://qiita.com/taro-hida/items/f677abe2bc3b689002b3

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

HerokuでWebサービス公開(PHP+MySQL)

はじめに

localhostでPHP+MySQLで開発したものを、Herokuで公開するためのもの
OS: mac
PHP: フレームワーク無し
DB: MySQL

1.Herokuに登録

https://www.heroku.com/
クレジットカードを登録しておきます。

2.Heroku CLIをインストール

https://devcenter.heroku.com/articles/heroku-cli

$ heroku login

3.アプリ作成

ここではデスクトップにheroku-appというディレクトリを作ります。

$ cd Desktop
$ mkdir heroku-app
$ cd heroku-app

アプリを作成します。アプリ名を指定しない場合は自動で割り振られます。

$ heroku create アプリ名

4.composer

composer.jsonというファイルを作成します。

touch composer.json

phpのバージョン7を指定します。これが正しいのか分かりませんがこれで動きました。
"ext-mbstring": "*" とすることでmb_xxxの関数(mb_strlenとか)が使えるようになります。指定しないでmb_xxxの関数を使うとCall to undefined functionエラーになります。

composer.json
{
    "require": {
      "php": "~7.0",
      "ext-mbstring": "*"
    }
 }

デフォルトのPHPが入っていることが前提です。

$ curl -sS https://getcomposer.org/installer | php
$ mv composer.phar /usr/local/bin/composer

ターミナルを再起動して確認します。大丈夫ならインストールします。

$ cd Desktop/heroku-app
$ composer -v
$ composer install

5.Gitリポジトリ作成

$ git init
$ git remote add heroku https://git.heroku.com/アプリ名.git
$ git remote -v

6.ClearDBアドオンをインストール

herokuでmysqlを使うための設定です。

$ heroku addons:add cleardb:ignite

7.Workbenchをインストール、設定

Workbenchを使うことで、phpMyAdminのようにGUIでデータベースを管理できます。
インストールが完了したら赤枠で囲ったMySQL Connectionsの横の+をクリックして設定画面を開きます。
スクリーンショット 2019-04-05 18.18.34.png

heroku configコマンドでCLEARDB_DATABASE_URLを確認します。

$ heroku config

これを元に入力していきます。CLEARDB_DATABASE_URL: mysql://ユーザ名:パスワード@サーバ名/データベース?reconnect=true
Connection Name: 適当な名前
Hostname: サーバ名
Username: ユーザ名
Password: パスワード
Default Schema: データベース

入力を終えたら、Test Connectionをクリック→上で入力したパスワードを入力→Connection Warning画面が出る→Continue AnywaySuccessfully made the MySQL connectionOKOK

8.phpMyAdminからDBエクスポート→WorkbenchにDBインポート

phpMyAdminからエクスポート

phpMyAdminからエクスポートしたいDBを選択→詳細にチェック→出力をファイルに保存する実行

Workbenchにインポート

7.で設定したものに接続してAdministraionタブからData Import/RestoreImport from Self-Contained Fileにチェックを入れてphpMyAdminからエクスポートしたsqlファイルを選択します。
Default Target Schemaを指定します。newの方ではなくてセレクトメニューでデフォルトのものを指定します。
なぜかフリーズしたように?選択できないときがありますが、謎です。
最後にStart Importをクリックします。

9.PHPのDB接続設定を変更する

local用から本番用に書き換える必要があります。

function.php
function dbConnect(){
  $db = parse_url($_SERVER['CLEARDB_DATABASE_URL']);
  $db['dbname'] = ltrim($db['path'], '/');
  $dsn = "mysql:host={$db['host']};dbname={$db['dbname']};charset=utf8";
  $user = $db['user'];
  $password = $db['pass'];
  $options = array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY =>true,
  );
  $dbh = new PDO($dsn,$user,$password,$options);
  return $dbh;
}

10.herokuへデプロイ

$ git add -A
$ git commit -m "commit"
$ git push heroku master

heroku openでブラウザを開いて表示してくれます。localと同じようにDBのデータ等ちゃんと表示できていれば完了です。

$ heroku open

11.WorkbenchでDBの中身を確認する

赤枠で囲ったSchemasTables
スクリーンショット 2019-04-06 7.52.37.png
各テーブルにマウスホバーすると、右にアイコンが出る(赤枠で囲った所)ので一番右のカレンダーみたいなアイコンをクリックするとデータが確認できます。または、SQL文を打ち込んで雷アイコンを押してもデータ確認ができます。
スクリーンショット 2019-04-06 8.15.19.png

その他エラー

・herokuはphpログファイルとかエラーが見れないようなので、見るにはini_set('display_errors',1)を一時的に使います。

Error Code: 2013. Lost connection to MySQL server〜というエラーが出る場合
https://stackoverflow.com/questions/10563619/error-code-2013-lost-connection-to-mysql-server-during-query

参考:

herokuでPHPからMySQL(ClearDB)を使うhttp://blog.a-way-out.net/blog/2014/12/11/heroku-php-mysql/
herokuがcomposer.lock必須になったのでcomposerの入れ方をメモしておくhttp://kayakuguri.github.io/blog/2015/08/25/composer-lock-require/
Heroku データベースの管理(ClearDB アドオンによる MySQL の利用)http://www.ownway.info/Ruby/heroku/how/management/database/cleardb
MySQL WorkbenchをMacにインストールする方法https://programmingnavi.com/77/
heroku環境でmb_xxx関数を使うと、Fatal error: Call to undefined function …のエラーが表示されるhttps://qiita.com/taro-hida/items/f677abe2bc3b689002b3

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

MySQLのInnoDBのトランザクション周りについて調べたことをまとめた

元々NoSQLしか使用していなく、今年に入って転職して初めて業務でMySQLを使用しだした。
雰囲気で使ってる感じがしてよくないなと思ったので、色々調べた。

今回まとめたのは、MySQLのInnoDBのトランザクションについての一部。
この記事でまとめる対象の事柄は、既に詳しく素晴らしい記事が存在するが、それなりに難しい内容なのでそれらを読みながら理解の補助的に、またメモ的にまとめたものが本記事。

また、「この概念を理解するには、事前に〇〇の概念の理解が必要で、それを理解するにはこの記事がわかりやすい」といった形で自分が後から振り返られるようにもまとめた。

この記事はあくまでも理解の補助と、どの概要を学んだ方が良いかということをまとめた記事なので、詳細な理解は紹介している記事を読んだ方が良い。

トランザクションの分離レベル

トランザクションにはACID属性というものがあって、その中のI(Isolation)が分離性と呼ばれるもの。

トランザクションの分離にはレベルが存在する。
詳細は以下の記事等を参照。

大事なのは、どの分離レベルにおいて、どんな不都合な読み込みが行われるかを認識することだと思う。

トランザクション分離レベル - Wikipedia

[RDBMS][SQL]トランザクション分離レベルについて極力分かりやすく解説 - Qiita

MySQLのInnoDBの Repeatable Read

MySQLのデフォルトのエンジンであるInnoDBのデフォルトのトランザクションの分離レベルはRepeatable Readである。

一般的に、Repeatable Readは、ダーティリードとファジーリードは防ぐことができるけれども、ファントムリードは防ぐことができない。

ファントムリードは、以下のようだとある。

別のトランザクションで挿入されたデータが見えることにより、一貫性がなくなる現象。

引用元: [RDBMS][SQL]トランザクション分離レベルについて極力分かりやすく解説 - Qiita

一般的なRepeatable Readの場合のシミュレーションすると以下のような感じ(実際にやったわけではない)

事前に以下のような操作を行なっているとする。

CREATE TABLE programming_langs (
    id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
    name VARCHAR(20)
);

show tables;

+-------------------+
| Tables_in_sample  |
+-------------------+
| programming_langs |
+-------------------+

INSERT INTO programming_langs (name) VALUES ('Go');

Repeatable Readの場合のトランザクションのシミュレーション

phantom.png

上記のように、左側のトランザクションがInsertしたデータを右側のトランザクションが読み込めてしまう。(ファントムリード)

しかし、MySQLの InnoDB は、 Repeatable Read であっても、別のトランザクションで挿入されたデータをうまいことやりくりしており、一貫性があるように見える。

実際にやってみた。

事前に以下のような操作を行なっているとする。

CREATE TABLE programming_langs (
    id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,
    name VARCHAR(20)
);

show tables;

+-------------------+
| Tables_in_sample  |
+-------------------+
| programming_langs |
+-------------------+

INSERT INTO programming_langs (name) VALUES ('Go');

Repeatable ReadのMySQL InnoDBでの操作を図にしたもの
(実際にMySQLでやってみたが、見易さを考慮して図にしている)

mvcc.png

ファントムリードが生じていないように見える。

これはなぜだろうか。実は、MVCC(MultiVersion Concurrency Control)という仕組みを用いているからである。

MVCC

MySQLのドキュメントによれば

Acronym for “multiversion concurrency control”. This technique lets InnoDB transactions with certain isolation levels perform consistent read operations; that is, to query rows that are being updated by other transactions, and see the values from before those updates occurred. This is a powerful technique to increase concurrency, by allowing queries to proceed without waiting due to locks held by the other transactions.

This technique is not universal in the database world. Some other database products, and some other MySQL storage engines, do not support it.

引用元: MySQLのドキュメント

上記を意訳しつつ、まとめると以下のような感じ。

  • MVCCを使用すると、特定のトランザクションの分離レベルのInnoDBで、一貫性のある読み取り操作が可能に
    • 他のトランザクションが更新した行を照会する際に、更新前の状態を見ることができる
  • 他のトランザクションのロックを待たずに、クエリを進めることができるのでMVCCだと、同時実行性を高めることができる

他のトランザクションが更新した行を照会する際に、更新前の状態を見ることができる

これがまさにMySQLのInnoDBではRepeatable Readであっても別のトランザクションで挿入されたデータをうまいことやりくりして、一貫性があるように見える(ファントムリードが起きない)ことに寄与している。

これはファントムリードでは、トランザクション内で挿入したデータを別のトランザクション内で見えてしまうが、それを回避することができるということだ。

MVCCの仕組みの説明は、以下がわかりやすいのでこちらを参照。
MySQLのMVCC - Qiita

公式でも詳しく仕組みが記されている。

MVCCが故の注意

行ロックをすることなく一貫性を出せるのと、複数のトランザクションが同一のテーブルを操作するとき、お互いのロックを待つことなく操作できるので、同時実行性が上がる。

しかし、行ロックをしないが故に生じ得るLost Updateという問題も存在する。
以下に詳しく記述されているので参照。
漢(オトコ)のコンピュータ道: InnoDBのREPEATABLE READにおけるLocking Readについての注意点

詳しくは上記の記事を参照した方が良いが、記事を読んだ際に頭を整理するためにこれまで書いてきた内容と合わせて、以下のようなメモをしながら読んだので載せておく。

  • Lost Update
    • MVCCは、行ロックをとるわけではないので2つのトランザクションが重なったときには、後で更新した方の値が最終的に反映される
  • Locking Read
    • Lost Updateを防ぐためには、Locking Readを使用する
      • Lost Update は行にロックをかけていないから生じるわけで、それならば行をロックすればええやんという感じ
        • 排他ロックと共有ロックを行える構文が存在する
          • 排他ロックと共有ロックはこの記事がわかりやすい
      • 逆説的な話だが、MVCCでRepeatable Readでもファントムリードを防げているのは、MVCCによって、DB_ROLL_PTRの値を見ているからで、Locking Readの場合はそうでなくファントムリードが生じる

DB_ROLL_PTR ... そのレコードの過去の値を持つundo log recordへのポインタ

引用元: MySQLのMVCC - Qiita

ロックの仕組み

MySQLのロックにはレコードロック、ギャップロック、ネクストキーロックの3つがある。
以下の記事が非常にわかりやすいので、参照。

MySQL(InnoDB)のネクストキーロックの仕組みと範囲を図解する - 備忘録の裏のチラシ

ただ、「レコードロックっていうのは実際は、インデックスレコードのロックだよ」みたいな感じでインデックスの知識が必要。
ここで必要なインデックスの知識も、ただクエリの性能向上のための便利なものという理解ではなく内部の仕組みの理解が必要みたいだ。(特にクラスタインデックスとセカンダリインデックス)

なので、上記のロックの記事を読む前に以下の記事等でインデックスの仕組みについて学んでからの方が良さそう。

インデックスの記事
漢(オトコ)のコンピュータ道: 知って得するInnoDBセカンダリインデックス活用術!

INDEX FULL SCANを狙う

MySQL :: MySQL 5.6 リファレンスマニュアル :: 14.2.13.2 クラスタインデックスとセカンダリインデックス

MySQL with InnoDB のインデックスの基礎知識とありがちな間違い - クックパッド開発者ブログ

MySQL(InnoDB)のインデックスについての備忘録 - What is it, naokirin?

参考にさせていただいたサイト

ACID属性

ACID (コンピュータ科学) - Wikipedia

トランザクション分離レベル

[RDBMS][SQL]トランザクション分離レベルについて極力分かりやすく解説 - Qiita

トランザクション分離レベル - Wikipedia

MVCC

MySQLのMVCC - Qiita

MySQLのドキュメント

ロック
MySQLでSELECT FOR UPDATEと行ロックの挙動を検証してみた - JUST FOR FUN

MySQL - InnoDBのロック関連まとめ - Qiita

世界の何処かで MySQL(InnoDB)の REPEATABLE READ に嵌る人を1人でも減らすために - KAYAC engineers' blog

doc/innodb.md at master · ichirin2501/doc

漢(オトコ)のコンピュータ道: InnoDBのREPEATABLE READにおけるLocking Readについての注意点

アプリケーションエンジニアが知っておくべきMySQLのロック - Qiita

MySQL(InnoDB)のネクストキーロックの仕組みと範囲を図解する - 備忘録の裏のチラシ

インデックス
漢(オトコ)のコンピュータ道: 知って得するInnoDBセカンダリインデックス活用術!

INDEX FULL SCANを狙う

MySQL :: MySQL 5.6 リファレンスマニュアル :: 14.2.13.2 クラスタインデックスとセカンダリインデックス

MySQL with InnoDB のインデックスの基礎知識とありがちな間違い - クックパッド開発者ブログ

MySQL(InnoDB)のインデックスについての備忘録 - What is it, naokirin?

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

RailsでMySQLに絵文字を保存したい

特に設定しなければ絵文字を書いて保存しようとすると、Incorrect string valueのエラーが発生します。

絵文字を保存する場合は、config/database.ymlのencodingをutfmb4にします。

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

また、テーブルを作る際もCHARSET=utfmb4を設定します。

create_table "test_table", options: "CHARSET=utf8mb4" do |t|
    t.string "name", null: false
  end

すでに、DBを作っている場合は一度DBをdropして、DBから作り直す必要があります。

補足(2019/04/07)

MySQLで絵文字(4バイト文字)が対応しているバージョンは5.5以降ですので、それ以前のバージョンには対応していません。

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

RailsでDBに絵文字を保存したい

特に設定しなければ絵文字を書いて保存しようとすると、Incorrect string valueのエラーが発生します。

絵文字を保存する場合は、config/database.ymlのencodingをutfmb4にします。

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

また、テーブルを作る際もCHARSET=utfmb4を設定します。

create_table "test_table", options: "CHARSET=utf8mb4" do |t|
    t.string "name", null: false
  end

すでに、DBを作っている場合は一度DBをdropして、DBから作り直す必要があります。

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