はじめに
特定の期間にユーザが更新したレコードの抽出を目的として、次のようなSOQLを作成し実行したところ
想定しているよりもクエリのレスポンスが悪い、時にはタイムアウトとなった経験はありませんか。
SELECT Id, Name FROM targetObject
WHERE fromDate < LastModifiedDate AND LastModifiedDate < toDate;
私も上記のようなLastModifiedDateを指定したSOQLのパフォーマンスで躓きました。
本記事ではLastModifiedDateをWHERE句に指定した場合に低パフォーマンスとなる原因について調査し、
その調査結果について備忘録的にまとめております。
低パフォーマンスの原因
原因はWHERE句でのLastModifiedDate指定において
インデックスが無効となるパターンが存在するためでした。
下記条件パターンのうち①と②はインデックスが有効となりますが、③はインデックスが無効となります。
①LastModifiedDate > DateField
②LastModifiedDate = DateField
③LastModifiedDate < DateField
LastModifiedDate < DateField でインデックスが無効となる理由
これはLastModifiedDateはインデックスを保持しておらず、
SOQLのクエリオプティマイザが内部的にLastModifiedDateをSystemModStampへと置き換えて
SystemModStampのインデックスを使用しているためです。
LastModifiedDateとSystemModStampはいずれも更新時間を保持する項目となりますが、
リファレンスを参照しますと更新契機が次のように異なることがわかります。
LastModifiedDate:ユーザがこのレコードを最後に変更した日時。
SystemModStamp:ユーザまたは自動化されたプロセス (トリガなど) によって最後に更新された日時。
端的に言えばSystemModStampの方が更新タイミングが多いため
項目間での更新時間については次の関係が成立します。
LastModifiedDate <= SystemModStamp
上記よりWHERE句指定パターン③ LastModifiedDate < DateField においては
LastModifiedDate < DateField < SystemModStamp
の関係が成立する可能性があり
SystemModStampで条件を判定した場合にレコードの抽出漏れが発生します。
このため③のWHERE句指定ではLastModifiedDateをSystemModStampへと置き換えることができずインデックスが有効となりません。
冒頭に提示したSOQLもパターン③が当てはまるため、インデックスが無効になっていたようです。
対応策について
SystemModStampが常にLastModifiedDateと同一の値を保持しており、
LastModifiedDate < DateField < SystemModStamp
とならないことが自明であれば、
SystemModStampをWHERE句の条件に指定するのが最も簡単な対応策となります。
ですが、大抵の場合はトリガ等でSystemModStampが更新されているでしょうし、
SystemModStampで代用できるか調査するぐらいであれば、この対応策は止めておいたほうがよいでしょう。
現実的な対応策としては、カスタムインデックスを付与した日付型カスタム項目を用意するものがあります。
ワークフロールール等でカスタム項目にLastModifiedDateの値コピーを行って同期をとっておけば、
WHERE句指定にカスタム項目を使用できますので、どのWHERE句指定パターンでもインデックスが有効となります。
参考ページ
Salesforce および Lightning プラットフォームのオブジェクトリファレンス システム項目
SOQL 実行時の LastModifiedDate と SystemModStamp のインデックス利用について
Force.com SOQL Performance Tips: LastModifiedDate vs SystemModStamp