20191126のMySQLに関する記事は8件です。

eager_load/preload/includes/joinsの違いメモ

動機

日頃Goしか書いてないGopherがRails書いたときにつまったActiveRecord、特にjoins周りの挙動が直感的によくわからなかったので実際にデータ用意して調べてみた
(メソッドの具体的な説明は他にたくさん記事があるのでそちらを参照してください。)

実行環境

Rails公式Docのサンプルブログアプリ
article.rbcomment.rb のモデルを使用。
今回は、それぞれ約25万行を用意した。

article.rb
class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
end
comment.rb
class Comment < ApplicationRecord
  belongs_to :article
end

Controllerから preload/eager_load/joins/includes を用い、それぞれの吐くSQLやパフォーマンスを比較してみる

0. 素

Article.limit(100)

SELECT `articles`.* FROM `articles` LIMIT 100
-- Completed 200 OK in 154ms (Views: 127.1ms | ActiveRecord: 15.7ms | Allocations: 20418)

まずは検証用にそのままで...

1. eager_load

Article.eager_load(:comments).limit(100)

SQL (1.9ms)  SELECT DISTINCT `articles`.`id` FROM `articles` LEFT OUTER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` LIMIT 100
SQL (575.4ms)  SELECT `articles`.`id` AS t0_r0, `articles`.`title` AS t0_r1, `articles`.`text` AS t0_r2, `articles`.`created_at` AS t0_r3, `articles`.`updated_at` AS t0_r4, `comments`.`id` AS t1_r0, `comments`.`commenter` AS t1_r1, `comments`.`body` AS t1_r2, `comments`.`article_id` AS t1_r3, `comments`.`created_at` AS t1_r4, `comments`.`updated_at` AS t1_r5 FROM `articles` LEFT OUTER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` WHERE `articles`.`id` 
IN (1, 2, 3, 4, 6, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19, 20, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157)
-- Completed 200 OK in 4869ms (Views: 4280.1ms | ActiveRecord: 585.9ms | Allocations: 2103879)

万能感ある。駆動表のキャッシュをしながら、絞り込みもできる。IN長すぎワロタ。

2. preload

Article.preload(:comments).limit(100)

Article Load (1.9ms)    SELECT `articles`.* FROM `articles` LIMIT 100
Comment Load (405.8ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`article_id` 
IN (1, 2, 3, 4, 6, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19, 20, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157)
-- Completed 200 OK in 4304ms (Views: 3883.6ms | ActiveRecord: 416.9ms | Allocations: 1658640)

eager_loadより省エネな感じ。ただ、最後のSELECT文がcommentsテーブルしか参照してないので絞り込みができない

3. includes

Article.includes(:comments).limit(100)

Article Load (1.5ms)  SELECT `articles`.* FROM `articles` LIMIT 100
Comment Load (384.1ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`article_id` 
IN (1, 2, 3, 4, 6, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19, 20, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157)
-- Completed 200 OK in 3737ms (Views: 3339.1ms | ActiveRecord: 395.3ms | Allocations: 1652329)

今回はpreloadと同じ挙動
(includes は場合によって挙動が変わる. 最後のSELECT文にないテーブルのwhereとかはできないので、そういう時はeager_loadになる)

def eager_loading?
  @should_eager_load ||=
    eager_load_values.any? ||
      includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
end

4. joins

Article.joins(:comments).limit(100)

Article Load (2.1ms)  SELECT `articles`.* FROM `articles` INNER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` LIMIT 100
-- Completed 200 OK in 67ms (Views: 56.1ms | ActiveRecord: 7.0ms | Allocations: 21703)

いつものアレ。安心感ある。

まとめ

そもそもActiveRecordは LazyLoad、すなわち 必要になったときにSQLが実行される
for文の中でSQLが複数回発行(N+1)されないために、こういう機構が必要になってくる。
(生SQLerの私にはこの感覚があんまりない)

後は、where使いたいなら eager_load 何もしないなら preload って感じ。
頭使いたくない人は 「とりあえず includes」 しとけばなんとかなる。
ただ、複数紐づくテーブル扱い出すと最終的に吐き出されるSQLが予測困難になるので、ちゃんと使い分けたいところ。
ちゃんと使い分けたい人はこの記事とか読みましょう。実装追ってて勉強になります。

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

Rails +MySQLのプロジェクトをherokuへデプロイ

RailsのデフォルトDBはSQLiteですが、
MySQLにしたものとして先に進めます
MySQL導入方法
https://qiita.com/shigeshige/items/02ce019e02bcfff14b9b
今回はheorkuへ会員登録したものとして先に進めます

production環境

実際にHerokuでデプロイした後もMySQLで動くようにします

デフォルトでfalseとなっている以下の箇所をtrueに変更

Railsは本番環境での動的な画像の表示がデフォルトでオフになっています。

config/environments/production.rb
# 以下の箇所を変更
config.assets.compile = true
config.assets.initialize_on_precompile=false

heroku だと、 sqlite3 がビルドできないので、開発環境でだけ使うよ、っていう宣言が必要
 (heroku では、 production の gem だけビルドされる)

↓を削除し

Gemfile
gem 'sqlite3'

代わりに↓を挿入する

Gemfile
gem 'sqlite3', group: [:development, :test]
gem 'mysql2'
gem 'rails_12factor', group: :production
$ bundle install
コミットしておきます

Heroku作成

$ heroku create
$ git push heroku master

cleardbアドオンを追加

後述するアドオンの追加のところで、Herokuにクレジットカード情報を登録しておく必要がある(お金はかかりません)ので、登録しておいてください。

$ heroku addons:create cleardb:ignite

heroku-postgresqlアドオンを削除

$ heroku addons:destroy heroku-postgresql -a <アプリ名>

DATABASE_URLを編集

$ heroku config | grep CLEARDB_DATABASE_URL
CLEARDB_DATABASE_URL:mysql2://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true

環境変数を設定

それぞれを設定する

heroku config:add DB_NAME='<データベース名>'
heroku config:add DB_USERNAME='<ユーザー名>'
heroku config:add DB_PASSWORD='<パスワード>'
heroku config:add DB_HOSTNAME='<ホスト名>'
heroku config:add DB_PORT='3306'

上記のURLをMySQL2に切り替えます。コピーペーストしつつmysql2に書き換えてください

heroku config:add DATABASE_URL='mysql2://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true'
heroku config
で確認できます

database.ymlに設定

database.yml
production:
  <<: *default
  database: <%= ENV['DB_NAME'] %> 
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>

Herokuにデプロイ

$ git push heroku master

$ heroku run rake db:create
$ heroku run rake db:migrate

テストデータがある場合
$ heroku run rake db:seed
$ heroku open

でデプロイできました!!!
色々とうまくいかない場合もありましたがなんとかできました
参考になれば幸いです

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

MySQLのjson型

はじめに

MySQL5.7.8以降はjson型が使えるようになりました。
JSONを慣れた開発者に対して選択肢が増えました。

数点をメモとして残ります。

The JSON Data Type: https://dev.mysql.com/doc/refman/8.0/en/json.html

WorkbenchからカラムをJSONに変更しようとするとサポートされないエラー

WorkbenchツールでテーブルのカラムをJSON型に設定してみると、サポートされませんってエラーが出る場合があります。

問題1:接続しているサーバは5.7.8以上ではない 

これはバージョンアップするしかないです。

問題2:MySQLサーバは5.7.8以上でもエラー

これはWorkbenchの設定問題かもしれません。
image.png

操作してみる

JSON文字列でインサート

insert into json_test(json_data) values('{"key1":"value1", "key2":"value2"}')

JSON_OBJECT関数でインサート

JSON_OBJECT関数を使って属性を設定する可能です。

insert into json_test(json_data) values(JSON_OBJECT('key11', 'value11', 'key22', 'value22'))

image.png

複数行をJsonArrayに変換

SELECT JSON_ARRAYAGG(JSON_OBJECT('userName', userName, 'email', email)) from User;

MySQL8.0のクライアントから接続で認証失敗?

mysql_native_passwordのユーザー変更で解消できます。

ALTER USER 'dbuser'@'localhost' IDENTIFIED WITH mysql_native_password BY 'dbUserPasswordxxxx';

トリガー作成できない?

問題1:権限ありません

ERROR 1419 (HY000) at line 3155: You do not have the SUPER privilege and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)

log_bin_trust_function_creatorsを1設定変更で回避できます。

問題2:DEFINER記載しないと作成権限があってもエラー

CREATE DEFINER=root@localhostTRIGGER xxx の感じでDEFINERを定義で回避できる場合があります。

MySQLのJSONメソッド

image.png

出典: https://dev.mysql.com/doc/refman/8.0/en/json-function-reference.html

以上

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

【laravel】 DBの存在するテーブルとカラムとカラムの型を取得するあとdoctrine/dbalでjson型が扱えない件

環境

MySQL 5.7
Laravel 5.8

存在するテーブル

tableNames = [];
foreach (DB::select('SHOW TABLES') as $table) {
    $dbName = config('database.connections.mysql.database');
    $tableNames[] = $table->{'Tables_in_' . $dbName};
}

存在するカラムとその型

型を取得

// 存在するカラムを取得
$columns = Schema::connection('mysql')->getColumnListing($tableName);

$columnTypes = [];
foreach ($columns as $column) {
    // カラムタイプを取得
    $columnTypes[$column] = Schema::connection('mysql')->getConnection()->getDoctrineColumn($tableName, $column)->toArray()['type'];
}

int の場合 Doctrine\DBAL\Types\IntegerType のようにとれる。
これはSQLの型とPHPの型をマッピングするクラス。

型判定

if ($type instanceof Doctrine\DBAL\Types\StringType) {
    // ごにょごにょ
}

json型でエラー

Doctrine DBALが2.4以下だとjson型がサポート外とのこと。
エラーが出るので以下のようにjson_arrayにマッピングさせると扱えるようになる。

DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('json', 'json_array');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLで照合順序の確認と変更

データベース単位

SELECT @@character_set_database, @@collation_database;
ALTER DATABASE COLLATE 'utf8_general_ci'

テーブル単位

show table status from データベース名;
ALTER TABLE テーブル名 COLLATE 'utf8_general_ci';

カラム単位

use information_schema;
select table_name, column_name, collation_name from columns  where table_schema="データベース名" and collation_name is not null

use データベース名
ALTER TABLE テーブル名 MODIFY COLUMN カラム名 varchar (255) COLLATE 'utf8_general_ci';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLのクエリログを収集したいときは

MySQLでログの設定を行いたい

自分用メモです。

my.cnfの場所を調べる

$ mysql --help | grep my.cnf
                      order of preference, my.cnf, $MYSQL_TCP_PORT,
/etc/my.cnf /etc/mysql/my.cnf /usr/local/etc/my.cnf ~/.my.cnf 

左から順に呼ばれていく。
環境を汚したくないので今回は /usr/local/etc/my.cnf をいじる。

my.cnf設定

[mysqld]
# ...
general_log=1
log_output=FILE
general_log_file=/var/log/mysql/query.log
# ...

上記を追記する。

反映確認

show variables like '%general_log%';
Variable_name Value
general_log ON
general_log_file /var/log/mysql/query.log

上記方法でダメなときは

set global general_log='ON';

のように直接書き換えてみる。

ログ確認

$ tail -f /var/log/mysql/query.log
Tcp port: 3306  Unix socket: /var/lib/mysql/mysql.sock
Time                 Id Command    Argument
191125 21:29:31   137 Query SHOW VARIABLES LIKE '%log%'

ログが流れていくことが確認できる。

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

【Rails環境構築】MySQL2が原因で「bundle install」失敗した時の対処法

はじめに

Railsの環境構築をローカルでしようとrails newからはじめたところ、bundle installができずに引っかかったため備忘録として残しておきます。

環境

OS : Mac OS Catalina 10.15.1
Ruby : 2.6.3p62
Rails : 6.0.1
Homebrew : 2.1.16
Bundle : 1.17.2

エラー

$ bundle install

省略(gemのインストール)

Installing mysql2 0.5.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

省略

An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds
before bundling.

ざっくり解釈した限りだと、「mysql2 0.5.2のインストール時にnative extention(CやC++で書かれるMySQLの拡張のライブラリ)のビルドに失敗したので、bundleの前にgem install mysql2 -v '0.5.2'で確認してください」とのことですが、私の場合root権限でこのコマンドを打っても解決せず...

解決法

結論から言うと、「ビルド時にOpenSSL公開鍵のPATH指定が必要」と「bundle configでPATHオプションをLDFLAGS、CPPFLAGSで別々に指定」で解決しました。

1.公開鍵のPATH確認

brew info openssl

省略

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"      ← ここ使用
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"   ← ここ使用

省略

2.それぞれPATHを指定

$ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"

それぞれ、「build.mysql2の現在のローカル値を置き換えています」と返答がきます。

3. bundle install (ここで終了)

$ bundle install

私の場合はこれで解決し、「rails new」でも問題なく作成できました。

失敗or解決に直接繋がらなかったコマンド

1. PATHの両指定

bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include"

2.mysqlの再起動

$ sudo mysql.server restart

その他

公開鍵、LDFLAGS、CPPFLAGSなど知識やAppleの独自TLS背景などまだまだ勉強不足でした💦
表記や解釈ミスなどあればご指摘頂けますと幸いです。

参考

こちらの記事がなければ解決できませんでした(本当に助かりました)。
https://qiita.com/fukudakumi/items/463a39406ce713396403
https://qiita.com/akito19/items/e1dc54f907987e688cc0
http://woshidan.hatenadiary.jp/entry/2017/01/21/150948

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

AWS Coud9でphpMyAdmin画面へアクセスしたときに 404|not foud エラーを解決する方法

Cloud9で開発環境を整えて、時間がたってからなぜかphpMyAdminへアクセスできず、ログイン画面で見れなくなる症状に躓きました。

いくら調べても解決しなかったんですが、もしかしたら稀にいるかもしれないと思って記事を書くことにしました。

解決のために事前チェックその1

サーバーを起動しているか確認

サーバー起動コマンド
$ ^Cec2-user:~/environment/project1 $ php -S $IP:$PORT
PHP 7.3.11 Development Server started at Mon Nov 25 15:22:41 2019
Listening on http://127.0.0.1:8080
Document root is /home/ec2-user/environment/project1
Press Ctrl-C to quit.

下記のコマンドで起動したサーバーは phpMyAdminのページへアクセスしても 404|not foudとなります。

サーバー起動コマンド(1)
$ ec2-user:~/environment/project1  $ php artisan serve
Laravel development server started: http://127.0.0.1:8000
サーバー起動コマンド(2)
$ ec2-user:~/environment/project1  $ php artisan serve --port=8080
Laravel development server started: http://127.0.0.1:8080

事前チェックその2

Laravelのプロジェクト(ディレクトリ)内でmysqld を起動しているか確認

mysqld起動コマンド
$ sudo service mysqld start
ec2-user:~/environment/project1  $ sudo service mysqld start
Starting mysqld:                                           [  OK  ]
mysqldの状態確認コマンド
$ ec2-user:~/environment/project1  $ sudo service mysqld status
mysqld (pid  20873) is running...                              [  OK  ]

running...と表示されていれば、起動中です。

一応、mysqld 停止コマンドも明記しておきます。

mysqldの停止コマンド
$ ec2-user:~/environment/project1  $ sudo service mysqld stop
Stopping mysqld:                                           [  OK  ]

事前チェックその3

phpMyAdminのURLを "https://~~.vfs.cloud9.~~.amazonaws.com/phpMyAdmin/index.php" まで指定している

それでも解決しなかった場合、Cloud9上で作成した他のプロジェクト内でmysqldが起動している可能性があるので、それを停止します。

(アクセス権限に関しても調べて変更してみましたが、結果的に関係ありませんでした。)
linuxコマンドに詳しい方はもうわかると思うので、読み進める必要はないです。

念のためlinux初心者の方のために細かいコマンド実行の様子を記しておきます。

まずはディレクトリの移動

親ディレクトリ移動
$ ec2-user:~/environment/project1  $ cd ../

そこでlsコマンドを実行すると他にもプロジェクトがあることがわかります。

ec2-user:~/environment $ ls
project1  project2  README.md 

project1 のディレクトリから project2 (別のプロジェクト)のディレクトリを移動します。

親ディレクトリ移動
$ ec2-user:~/environment/  $ cd project2

その中で再度、mysqldの状態を確認してみると、、、

mysqldの状態確認コマンド
$ ec2-user:~/environment/project2 $ sudo service mysqld status
mysqld (pid  20873) is running...                              [  OK  ]

動いてる。。。。(このプロジェクト、ずっと触ってなかったのに、、)

それで、もしかしたらと思い停止コマンドを実行して停止し、
project1 の方のディレクトリに移ってから、mysqldを起動してみると

mysqldの停止コマンド
$ ec2-user:~/environment/project2 $ sudo service mysqld stop
Stopping mysqld:                                           [  OK  ]
$ ec2-user:~/environment/  $ cd ../project1
ec2-user:~/environment/project1 $ sudo service mysqld start
Starting mysqld:                                           [  OK  ]

無事表示されました!!

phpmyadmin.png

以上です。 うっかり他のプロジェクトでのmysqldを停止し損ねて、
別プロジェクトで使おうとしたときに起きた症状の対策方法でした。

AWSはまだ触り始めたばかりなので、わからないことだらけで苦戦していますが、
上記の方法以外で解決方法をしっていたり、設定方法をご存知の方がいらっしゃいましたら
ぜひコメントいただけるとありがたいです。

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