- 投稿日:2019-11-26T23:58:31+09:00
eager_load/preload/includes/joinsの違いメモ
動機
日頃Goしか書いてないGopherがRails書いたときにつまったActiveRecord、特にjoins周りの挙動が直感的によくわからなかったので実際にデータ用意して調べてみた
(メソッドの具体的な説明は他にたくさん記事があるのでそちらを参照してください。)実行環境
Rails公式Docのサンプルブログアプリ
のarticle.rb
とcomment.rb
のモデルを使用。
今回は、それぞれ約25万行を用意した。article.rbclass Article < ApplicationRecord has_many :comments, dependent: :destroy validates :title, presence: true, length: { minimum: 5 } endcomment.rbclass Comment < ApplicationRecord belongs_to :article endControllerから
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?) end4. 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が予測困難になるので、ちゃんと使い分けたいところ。
ちゃんと使い分けたい人はこの記事とか読みましょう。実装追ってて勉強になります。
- 投稿日:2019-11-26T23:55:32+09:00
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=falseheroku だと、 sqlite3 がビルドできないので、開発環境でだけ使うよ、っていう宣言が必要
(heroku では、 production の gem だけビルドされる)↓を削除し
Gemfilegem 'sqlite3'代わりに↓を挿入する
Gemfilegem 'sqlite3', group: [:development, :test] gem 'mysql2' gem 'rails_12factor', group: :production$ bundle install コミットしておきますHeroku作成
$ heroku create $ git push heroku mastercleardbアドオンを追加
後述するアドオンの追加のところで、Herokuにクレジットカード情報を登録しておく必要がある(お金はかかりません)ので、登録しておいてください。
$ heroku addons:create cleardb:igniteheroku-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.ymlproduction: <<: *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でデプロイできました!!!
色々とうまくいかない場合もありましたがなんとかできました
参考になれば幸いです
- 投稿日:2019-11-26T21:02:00+09:00
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以上でもエラー
操作してみる
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'))複数行を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メソッド
出典: https://dev.mysql.com/doc/refman/8.0/en/json-function-reference.html
以上
- 投稿日:2019-11-26T18:26:58+09:00
【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');
- 投稿日:2019-11-26T18:14:18+09:00
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';
- 投稿日:2019-11-26T16:00:32+09:00
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%'ログが流れていくことが確認できる。
- 投稿日:2019-11-26T11:02:11+09:00
【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
- 投稿日:2019-11-26T00:18:50+09:00
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.mdproject1 のディレクトリから 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 ]無事表示されました!!
以上です。 うっかり他のプロジェクトでのmysqldを停止し損ねて、
別プロジェクトで使おうとしたときに起きた症状の対策方法でした。AWSはまだ触り始めたばかりなので、わからないことだらけで苦戦していますが、
上記の方法以外で解決方法をしっていたり、設定方法をご存知の方がいらっしゃいましたら
ぜひコメントいただけるとありがたいです。