Techioz Blog

作成または初期化します。データベースとサーバーの負荷を最小限に抑える方法

概要

私は Ruby on Rails フレームワークで Ruby で書いています。仮定の状況が理解できません。 get_date 関数が約 1,000 万個の要素の配列を返したとします。各要素には、fk、name、detail のフィールドが含まれます。 同じフィールドを含むテーブルもあります。部分的に埋まっています。 fk - 一意のインデックス付きフィールド。 私の仕事は、テーブルにエントリが存在するかどうかを確認することです。記録がない場合は作成する必要があります。レコードが存在する場合は、名前フィールドと詳細フィールドが無関係になる可能性があるため、それらのフィールドを更新する必要があります。 データベースとサーバーへの負荷をできるだけ少なくするコードを書く必要があります

私は提案します:

get_data.each do |item|
          field = MyField.find_or_initialize_by_fk(item['fk'])
          field.update_attributes(
              {
                  :name       => item['name'],
                  :detail    => item['detail']
              }
          )
          field.save
end

または

get_data.each do |item|
          field = MyField.find_or_create_by_fk(item['fk'])
          field.update_attributes(
              {
                  :name       => item['name'],
                  :detail    => item['detail']
              }
          )
end

これらともう一方のどちらが良いでしょうか?

解決策

最初のバージョンの方が優れています。これは、既存の可能性のあるレコードを検索するクエリと、既存のレコードを更新するか新しいレコードを作成するクエリの 2 つのクエリだけを実行するためです。

2 番目のバージョンは、既存のレコードが見つからなかった場合にデータベースに対して 3 つのリクエストを実行します。1 つはレコードの検索を試み、2 番目は新しいレコードを作成し、3 番目は新しく作成されたレコードを更新します。

ただし、代わりに、1 つのクエリで新しいレコードを挿入または既存のレコードを更新する upsert を使用することをお勧めします。

get_data.each do |item|
  MyField.upsert(
    { fk: item['fk'], name: item['name'], detail: item['detail'] },
    on_duplicate: 'name = EXCLUDED.name, detail = EXCLUDED.detail',
    unique_by: :fk
  )
end

たった 1 回のクエリで大量のレコードのバッチを挿入または更新できる upsert_all もあることに注意してください。 get_data の実装方法と使用方法に少し依存します。ただし、一般的な考え方は、レコードをバッチでロードし、1 つのクエリで各バッチを upsert_all することです。

もう 1 つのさらにパフォーマンスの高い方法は、SQL で 1 つのクエリを使用してこれを直接実行し、おそらく SQL の UPSERT コマンドを再度使用することです。それがどのように見えるかは、使用中のデータベース テーブルの正確なスキーマによって異なります。