In rough order of how frequently I see ’em:
- There are different workloads running on the server (like a backup is running right now.)
- You have a different query plan due to parameter sniffing.
- The query changed, like someone did a deployment or added a field to the select list.
- You have a different query plan due to a statistics change.
- You’re getting a different share of hardware (perhaps someone changed a hardware setting, introduced a noisy neighbor VM, or you have hardware competition in the cloud.)
- Someone added or removed an index.
- Someone added or removed a trace flag or other server-level config setting.
- Your application itself changed, and it’s not able to digest the results as quickly.
- Someone patched, and it had an unexpected side effect.
- You have the same plan, but different memory grants.
- Someone’s modifying more rows at a time, so you’re hitting lock escalation.
- Your query is being throttled by Resource Governor.
- It never really was that fast to begin with.
And starting with SQL Server 2017 (if you have columnstore indexes) and SQL Server 2019 (for everyone):
- You’re getting a different memory grant due to adaptive memory grants.
- You’re getting a different join type due to the adaptive join threshold changing.