orders = Order.objects.exclude(status=Order.CART).filter(Q(user=user) | Q(account_number=account_number))
page = Paginator(orders, per_page=10).page(request.GET.get('page') or 1)
# Query_time: 49.414771 Lock_time: 0.000148 Rows_sent: 1 Rows_examined: 412843
SELECT COUNT(*) FROM `order_order` WHERE (NOT (`order_order`.`status` = 4) AND (`order_order`.`user_id` = 12345 OR `order_order`.`account_number` = '123456'));
class Order(models.Model):
SUBMITTED, CART, SHIPPED, PARTIAL_SHIPPED, PROCESSED = 1, 4, 5, 6, 7
STATUS_CHOICES = (
(CART, 'Cart'),
(SUBMITTED, 'Submitted on Website'),
(PROCESSED, 'Processing'),
(PARTIAL_SHIPPED, 'Partial Shipped'),
(SHIPPED, 'Shipped'),
)
status = models.IntegerField('Order Status', choices=STATUS_CHOICES, default=CART, db_index=True)
user = models.ForeignKey('account.UserProfile', null=True, blank=True)
account_number = models.CharField(max_length=20, blank=True, db_index=True)
mysql> explain SELECT COUNT(*)
-> FROM `order_order`
-> WHERE `order_order`.`status` != 4
-> AND (`order_order`.`user_id` = 12345 OR `order_order`.`account_number`
-> = 123456);
+----+-------------+-------------+------+-------------------------------------------------------------------------------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+-------------------------------------------------------------------------------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | order_order | ALL | order_order_fbfc09f1,order_order_979d4f1e,order_order_48fb58bb,order_order_309e75c7 | NULL | NULL | NULL | 338275 | Using where |
+----+-------------+-------------+------+-------------------------------------------------------------------------------------+------+---------+------+--------+-------------+
1 row in set (0.16 sec)
mysql> show indexes in order_order;
+-------------+------------+----------------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------------+------------+----------------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| order_order | 0 | PRIMARY | 1 | id | A | 311492 | NULL | NULL | | BTREE | | |
| order_order | 1 | order_order_8df3c379 | 1 | order_number | A | 311492 | NULL | NULL | | BTREE | | |
| order_order | 1 | order_order_fbfc09f1 | 1 | user_id | A | 8 | NULL | NULL | YES | BTREE | | |
| order_order | 1 | order_order_979d4f1e | 1 | account_number | A | 311492 | NULL | NULL | | BTREE | | |
| order_order | 1 | order_order_48fb58bb | 1 | status | A | 10 | NULL | NULL | | BTREE | | |
| order_order | 1 | order_order_482713d4 | 1 | user_id | A | 12 | NULL | NULL | YES | BTREE | | |
| order_order | 1 | order_order_482713d4 | 2 | account_number | A | 155746 | NULL | NULL | | BTREE | | |
| order_order | 1 | order_order_fb3214ea | 1 | status | A | 215 | NULL | NULL | | BTREE | | |
| order_order | 1 | order_order_fb3214ea | 2 | account_number | A | 215 | NULL | NULL | | BTREE | | |
| order_order | 1 | order_order_55eb7e10 | 1 | status | A | 215 | NULL | NULL | | BTREE | | |
| order_order | 1 | order_order_55eb7e10 | 2 | user_id | A | 215 | NULL | NULL | YES | BTREE | | |
+-------------+------------+----------------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
mysql> explain SELECT COUNT(*) FROM `order_order` WHERE `order_order`.`status` != 4 AND (`order_order`.`user_id` = 12345 OR `order_order`.`account_number` = 123456);
+----+-------------+-------------+------+-------------------------------------------------------------------------------------------------------------------------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+-------------------------------------------------------------------------------------------------------------------------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | order_order | ALL | order_order_fbfc09f1,order_order_979d4f1e,order_order_48fb58bb,order_order_482713d4,order_order_fb3214ea,order_order_55eb7e10 | NULL | NULL | NULL | 311492 | Using where |
+----+-------------+-------------+------+-------------------------------------------------------------------------------------------------------------------------------+------+---------+------+--------+-------------+
Ok, you're not giving much of a chance to the query planner either. The cardinality of status is 10, so status!=4 potentially means "give me 90% of the rows". The cardinality of user_id is a mere 12, which potentially means "give me 8% of the rows". Your query could easily return 30.000 rows, according to the above (since you're OR'ing user_id and account_number).
That said, a query time of 49 seconds is absurd for just 300.000 rows even for a full table scan, and you should look into why.
Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment
order_order | InnoDB | 10 | Compact | 672252 | 1765 | 1186988032 | 0 | 85639168 | 822083584 | 716087
Maybe Users doesn't belong in the Orders table? You could move the user to a different table which stores the Order.id <-> user_id relation, which would give a fast lookup on user_id and thus easy access to the Order.id index.