Opened 13 months ago

Closed 13 months ago

Last modified 13 months ago

#34811 closed Bug (worksforme)

Queryset filter Q order changed

Reported by: Edward Villegas-Pulgarin Owned by: nobody
Component: Database layer (models, ORM) Version: 4.2
Severity: Normal Keywords: filter, Q
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Using filters in order explicitly expressed by user is important because take advantage of indexes.

When apply this query

TaxInput.objects.all(
                force_visibility=True
            ).filter(
                Q(filling_id=pk, updated_at__gt=last_engine.created_at)
                | Q(filling_id=pk, deleted__gt=last_engine.created_at),
            )

the MySQL generated query is

SELECT
  *
FROM
  `taxobjects_taxinput`
WHERE
  ( `taxobjects_taxinput` . `filling_id` = ?
    AND `taxobjects_taxinput` . `updated_at` > ? )
  OR ( `taxobjects_taxinput` . `deleted` > ?
    AND `taxobjects_taxinput` . `filling_id` = ? )
LIMIT
  ?

But should be

SELECT
  *
FROM
  `taxobjects_taxinput`
WHERE
  ( `taxobjects_taxinput` . `filling_id` = ?
    AND `taxobjects_taxinput` . `updated_at` > ? )
  OR ( `taxobjects_taxinput` . `filling_id` > ?
    AND `taxobjects_taxinput` . `deleted` = ? )
LIMIT
  ?

This change of order create a 10x factor in query time, because this table has index filing_id, and also index (filing_id, deleted).

Using Django 4.2.1 and SafeDelete 1.3.1 (because this is used force_visibility=True in all manager).

Change History (2)

comment:1 by David Sanders, 13 months ago

Resolution: worksforme
Status: newclosed

Hi,

Thanks for the report though I don't see an issue with Django here. There are many reasons why a database will choose to ignore indexes but sadly parameter ordering is not one of those. (To test this I recreated your setup and MySQL reported that the indexes are a possibility both ways.)

I'd encourage you to seek help from one of the Django support channels where there are many friendly people who are eager to help: https://www.djangoproject.com/community/

You'll likely need to paste detailed descriptions of your models, query, setup & any query plans (you may even be asked to paste memory settings etc as this will have an impact on query planning).

If it is discovered there is an issue with Django feel free to reopen this with links to discussion & findings.

Good luck!

comment:2 by Simon Charette, 13 months ago

If that can be of any help, we've observed that MySQL's query planer can be pretty bad at using composite indexes when OR conditions are involved and had to resort to using UNION to target them.

Re-writing your lookup to move the filling_id=pk outside of the OR, since it's used by both, might help MySQL figuring out that the filing_id index can be used at least.

TaxInput.objects.filter(
    filling_id=pk,
    Q(updated_at__gt=last_engine.created_at) | Q(deleted__gt=last_engine.created_at)
)
SELECT
  *
FROM
  `taxobjects_taxinput`
WHERE
    `taxobjects_taxinput` . `filling_id` = ?
    AND (
        `taxobjects_taxinput` . `updated_at` > ?
        OR `taxobjects_taxinput` . `deleted` > ?
    )

Otherwise I'm pretty sure that using union will perform great but it comes with some ORM limitations #28519.

TaxInput.objects.filter(
    filling_id=pk,
    updated_at__gt=last_engine.created_at,
).union(
    TaxInput.objects.filter(
        filling_id=pk,
        deleted__gt=last_engine.created_at,
    )
)
SELECT
  *
FROM
  `taxobjects_taxinput`
WHERE
    `taxobjects_taxinput` . `filling_id` = ?
    AND `taxobjects_taxinput` . `updated_at` > ?
UNION
SELECT
  *
FROM
  `taxobjects_taxinput`
WHERE
    `taxobjects_taxinput` . `filling_id` = ?
    AND `taxobjects_taxinput` . `deletedt` > ?
Last edited 13 months ago by Simon Charette (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top