N+1 means: one query to get the list, then one extra query per row. With 2,000 records, that is 2,001 round trips.
The slow version
for order in self.env['sale.order'].search([('state', '=', 'sale')]):
total += len(self.env['sale.order.line'].search([('order_id', '=', order.id)]))
Every pass of the loop hits the database again.
The fix
Ask once, group in the database:
data = self.env['sale.order.line'].read_group(
[('order_id.state', '=', 'sale')],
['order_id'], ['order_id'],
)
total = sum(r['order_id_count'] for r in data)
For plain field access, the ORM prefetches in batches of 1,000 as long as you iterate over the same recordset. Do not rebuild a new recordset inside the loop with browse(order.id), because that breaks prefetching.
How to spot it
Start Odoo with --log-level=debug_sql and run the screen. Seeing the same query shape repeat many times is the sign.
Rule of thumb
If a query sits inside a for, ask: can one read_group, mapped or search before the loop do this?