こんにちは。oda@エンジニア1年目です。
先日、マイグレーションファイルを使って、テーブルにInteger型のカラムを追加しました。
必要なレコードにのみデータを入れて、その他はnullにしていたところ、次のような指摘をいただきました。
「default: 0
を設定しておくのが自然」
※今回は0
とnull
が同じ意味合いになるカラムでした。
defaultを設定するか、nullとするかによる違いを調べてみたところ、次のようなものがありそうでした。
- レコードの検索にかかる時間
- レコードの検索や集計の結果
今回は、以下に焦点を当てて調べてみました。
null
or0
でないレコードを検索するときにかかる時間
それでは、実際にプログラムを動かしながら、確認していきたいと思います。
(以下、Rails 7.0.2.3、PostgreSQL14.2を使用しています。)
事前準備
まずは、Products(商品)テーブルを作成します。
bin/rails g model Products name:string
次に、サンプルデータとして、100,000件のレコードを作成します。
100000.times do |i| Product.create(name: "product#{i}") end
このProductsテーブルに、stock(在庫)カラムを追加し、在庫のある商品は在庫数を、ない商品はnull
or0
を入れていきます。
defaultを設定しない場合
次のマイグレーションファイルを作成、実行します。
今回は1,000件ごとに、在庫を1入れるようにしてみました。
class AddStockToProducts < ActiveRecord::Migration[7.0] def change add_column :products, :stock, :integer products = Product.where('id % 1000 = ?', 0) products.update_all(stock: 1) end end
default: 0を設定する場合
次のマイグレーションファイルを作成、実行します。
class AddStockToProducts < ActiveRecord::Migration[7.0] def change add_column :products, :stock, :integer, default: 0, null: false # ←ここを修正 products = Product.where('id % 1000 = ?', 0) products.update_all(stock: 1) end end
検証
それでは、rails consoleでそれぞれ確認していきます。
defaultを設定しない場合
以下のコマンドを実行します。
Product.where(stock: 1)
実行にかかった時間は次のとおりでした。
1回目:9.9ms 、2回目:11.8ms 、3回目:13.9ms
default: 0を設定する場合
同じく、以下のコマンドを実行します。
Product.where(stock: 1)
実行にかかった時間は次のとおりでした。
1回目:9.8ms 、2回目:12.6ms 、3回目:12.6ms
結果を整理
default | 1回目 | 2回目 | 3回目 |
---|---|---|---|
なし | 9.9ms | 11.8ms | 13.9ms |
あり | 9.8ms | 12.6ms | 12.6ms |
どちらの場合であっても、明確な差はありませんでした。
結果の考察
私はdefaultを設定した方が、時間が短くなると思っていました。
それは、null
が含まれていると検索に時間がかかるという話を聞いたことがあったからです。
今回、思っていたとおりにはならなかったので、その理由を考えてみました。
まず、そもそも、なぜ「null
が含まれていると検索に時間がかかる」のかということを調べてみたところ、IS NULL
が含まれているとインデックスが利用できないので、遅くなるとの情報が見つかりました。
私は、上記の検証をした時に、インデックスの設定もIS NULL
を含めた検索もしていませんでした。
この点について、再度検証してみます。
再検証
マイグレーションファイルにadd_index :products, :stock
を加えて、IS NULL
を使う場合と使わない場合を検証してみたところ、結果は、次のようになりました。
IS NULL
を使った時にかかる時間(defaultを設定しない)
Product.where(stock: nil) # 発行されるSQL # SELECT "products".* FROM "products" WHERE "products"."stock" IS NULL
default | 1回目 | 2回目 | 3回目 |
---|---|---|---|
なし | 36.3ms | 36.3ms | 39.7ms |
IS NULL
を使わなかった時にかかる時間(default: 0を設定する)
Product.where(stock: 0) # 発行されるSQL # SELECT "products".* FROM "products" WHERE "products"."stock" = $1 [["stock", 0]]
default | 1回目 | 2回目 | 3回目 |
---|---|---|---|
あり | 38.0ms | 35.7ms | 38.5ms |
再検証結果の考察
IS NULL
を使っても、使わなくても結果に明確な差は現れませんでした。
そこで、今回使用したPostgreSQLのドキュメントを確認してみたところ、IS NULL
を使ってもインデックス検索ができるとのことでした。
過去バージョンのドキュメントも見ることができたので、順番に見ていくとver8.2からver8.3に上がった時に、IS NULL
を使ってもインデックス検索ができると変わっていました。
このため、結果に明確な差が現れなかったと考えています。
(参考)
ver8.2:https://www.postgresql.jp/document/8.2/html/indexes-types.html
ver8.3:https://www.postgresql.jp/document/8.3/html/indexes-types.html
まとめ
今回は、検証前に自分の考えていた結果とは、違う結果となりました。
それにより、より深く調べたり、違う角度から検証したりすることができました。
今後も自分の手を動かしながら、一つ、一つ学んでいきたいと思います!
最後まで、ご覧いただきありがとうございました!