Search code examples
mysqlquery-optimization

Optimize crazy MySQL query


I need to optimise this insane looking query (legacy code):

SELECT
  E.eventId,
  E.currency,
  COALESCE(ROUND(UR.ratings, 2), 0) as ratings,
  COALESCE(UR.ratingCount, 0) as ratingCount,
  E.shopSpaceAvail,
  E.floorPlanImage,
  COALESCE(O.goingCount, 0) as goingCount,
  (COALESCE(O.goingGroup, '')) as goingGroup,
  E.userId,
  E.name,
  E.withoutTicket,
  E.mainImage,
  E.mainImageThumb,
  E.privateEvent,
  E.location,
  E.locationLatitude,
  E.locationLongitude,
  E.country3Code,
  E.country,
  E.city,
  E.description,
  E.startDt as startDt_formatted,
  E.endDt as endDt_formatted,
  (
    SELECT
      COUNT(*)
    FROM
      eventIntresteds
    WHERE
      eventId = E.eventId
  ) as interestedCount,(
    CASE WHEN "kaka" = "" THEN 0 ELSE (
      SELECT
        COUNT(*)
      FROM
        eventIntresteds as EI3
      WHERE
        EI3.eventId = E.eventId
        AND EI3.userId IN (
          48,
          1872,
          2039,
          67132,
          1076,
          1880,
          3504,
          3641,
          4575,
          3080,
          67129,
          67130,
          67134
        )
    ) END
  ) as mutualInterestedCount,
  COALESCE(
    (
      SELECT
        MIN(adultPrice)
      FROM
        eventTickets as ET
      WHERE
        ET.deleted = '0'
        AND ET.eventId = E.eventId
        AND ET.eventTicketType = 'Normal'
    ),
    0
  ) as minPrice,
  (
    CASE WHEN 'kaka' = '' THEN '2' WHEN (
      (
        SELECT
          COUNT(*)
        FROM
          eventIntresteds EI1
        WHERE
          EI1.eventId = E.eventId
          AND EI1.userId = 2162
      ) > 0
    ) THEN '1' ELSE '0' END
  ) as isInterested,
  (
    ROUND(
      (
        (
          3959 * acos(
            cos(radians(0)) * cos(radians(E.locationLatitude)) * cos(radians(E.locationLongitude) - radians(0)) + sin(radians(0)) * sin(radians(E.locationLatitude))
          )
        ) * 1.67
      ),
      4
    )
  ) as distance,
  (
    CASE WHEN E.privateEvent = '0'
    OR E.userId = 2162 THEN '1' WHEN 'kaka' = '' THEN '0' WHEN (
      (
        SELECT
          COUNT(*)
        FROM
          userNotifications UN
        WHERE
          UN.eventId = E.eventId
          AND UN.userId = 2162
          AND UN.notificationType = 'eventInvite'
      ) = 0
    ) THEN '0' ELSE '1' END
  ) as isprivateEvent,
  (
    CASE WHEN (
      E.privateEvent = '0'
      or E.userId = 2162
    ) THEN 1 ELSE (
      SELECT
        COUNT(*)
      FROM
        invites AS I
      WHERE
        I.eventId = E.eventId
        AND I.inviteType = 'Event'
        AND I.deleted = '0'
        AND I.userId = 2162
    ) END
  ) as privateHavingCheck,
  (
    SELECT
      COUNT(Distinct O1.shopId)
    FROM
      orders O1
      JOIN shops S1 ON (S1.shopId = O1.shopId)
    WHERE
      O1.eventId = E.eventId
      AND S1.deleted = '0'
      AND S1.blocked = '0'
      AND O1.orderStatus = 'Success'
      AND O1.paymentStatus = 'Success'
      AND O1.orderType = 'shopBooth'
      AND O1.refundId = ''
  ) as shopCount
FROM
  events AS E
  JOIN users U ON (
    U.userId = E.userId
    AND U.blocked = '0'
  )
  LEFT JOIN (
    SELECT
      COUNT(*) as ratingCount,
      AVG(ratings) as ratings,
      eventId
    FROM
      userRatings
    WHERE
      userRatings.blocked = '0'
      AND userRatings.deleted = '0'
    GROUP BY
      eventId
  ) as UR ON (UR.eventId = E.eventId)
  LEFT JOIN (
    SELECT
      COUNT(*) as goingCount,
      GROUP_CONCAT(userId) as goingGroup,
      eventId
    FROM
      orders AS O
    WHERE
      O.orderStatus = 'Success'
      AND O.paymentStatus = 'Success'
      AND orderType = 'EventTicket'
    GROUP BY
      eventId
  ) as O ON (O.eventId = E.eventId)
WHERE
  E.blocked = '0'
  AND E.deleted = '0'
  AND E.approved = '1'
  AND E.privateEvent = '0'
  AND E.endDt >= now()
Having
  eventId != 0
  AND isprivateEvent = '1'
  AND distance <= 1000000
ORDER BY
  minPrice DESC,
  minPrice DESC
LIMIT
  0, 10;

I tried to create indexes for this, but nothing seems to help. The query is super slow. The wort part seems to be this:

eventId FROM orders AS O WHERE O.orderStatus='Success' AND O.paymentStatus='Success' AND orderType='EventTicket' GROUP BY eventId) as O ON (O.eventId=E.eventId)

This scans 10K+ rows according to explain(). I tried a compound index on (orderStatus,paymentStatus,orderType) but it doesn't help.

Any suggestions about how to quickly optimise this query? I know it should be refactored, but there is no time for that. I also know it's bad, so I don't want it to be super fast. But at this point any speed up would be appreciated.

This is MySQL 5.7.

EDIT:

Here is the explain:

+----+--------------------+-----------------+------------+-------------+--------------------------------------------------------------------------------------------------------------------+--------------------------------------------------+----------+----------------------------------------+-------+----------+-----------------------------------------------------------------------------------+
| id | select_type        | table           | partitions | type        | possible_keys                                                                                                      | key                                              | key_len  | ref                                    | rows  | filtered | Extra                                                                             |
+----+--------------------+-----------------+------------+-------------+--------------------------------------------------------------------------------------------------------------------+--------------------------------------------------+----------+----------------------------------------+-------+----------+-----------------------------------------------------------------------------------+
|  1 | PRIMARY            | E               | NULL       | ref         | userId,combo,events_idx_blocke_delete_approv_privat_enddt                                                          | events_idx_blocke_delete_approv_privat_enddt     | 4        | const,const,const,const                |   359 |    33.33 | Using index condition; Using where; Using temporary; Using filesort               |
|  1 | PRIMARY            | U               | NULL       | eq_ref      | PRIMARY,blocked,users_idx_blocked_userid                                                                           | PRIMARY                                          | 8        | db.E.userId                    |     1 |    50.00 | Using where                                                                       |
|  1 | PRIMARY            | <derived9>      | NULL       | ref         | <auto_key0>                                                                                                        | <auto_key0>                                      | 8        | db.E.eventId                   |     2 |   100.00 | NULL                                                                              |
|  1 | PRIMARY            | <derived10>     | NULL       | ref         | <auto_key0>                                                                                                        | <auto_key0>                                      | 8        | db.E.eventId                   |    18 |   100.00 | NULL                                                                              |
| 10 | DERIVED            | O               | NULL       | index_merge | paymentStatus,orderStatus,orderType,eventIdAndDate,eventId,complexIdx3,complexIdx4,complexIdx5,sds                 | paymentStatus,orderStatus,orderType              | 22,22,22 | NULL                                   | 10019 |   100.00 | Using intersect(paymentStatus,orderStatus,orderType); Using where; Using filesort |
|  9 | DERIVED            | userRatings     | NULL       | index       | complexIdx1,eventId                                                                                                | eventId                                          | 8        | NULL                                   |    43 |    25.00 | Using where                                                                       |
|  8 | DEPENDENT SUBQUERY | O1              | NULL       | range       | shopId,paymentStatus,orderStatus,orderType,refundId,eventIdAndDate,eventId,complexIdx3,complexIdx4,complexIdx5,sds | complexIdx3                                      | 44       | NULL                                   |    78 |     2.50 | Using index condition; Using where                                                |
|  8 | DEPENDENT SUBQUERY | S1              | NULL       | eq_ref      | PRIMARY,shopId,blocked,deleted,shops_idx_deleted_blocked_shopid                                                    | PRIMARY                                          | 8        | db.O1.shopId                   |     1 |    80.93 | Using where                                                                       |
|  7 | DEPENDENT SUBQUERY | I               | NULL       | ref         | userId,eventId,invites_idx_invitet_deleted_userid_eventid                                                          | invites_idx_invitet_deleted_userid_eventid       | 39       | const,const,const,db.E.eventId |     1 |   100.00 | Using index                                                                       |
|  6 | DEPENDENT SUBQUERY | UN              | NULL       | ref         | userId,evId,notType,combo,usernotifications_idx_userid_notificati_eventid                                          | usernotifications_idx_userid_notificati_eventid  | 38       | const,const,db.E.eventId       |   368 |   100.00 | Using index                                                                       |
|  5 | DEPENDENT SUBQUERY | EI1             | NULL       | eq_ref      | eventId_2,eventId,userId,eventintresteds_idx_userid_eventid                                                        | eventId_2                                        | 16       |db.E.eventId,const             |     1 |   100.00 | Using where; Using index                                                          |
|  4 | DEPENDENT SUBQUERY | ET              | NULL       | ref         | eventId,type,deleted,delEvId,eventtickets_idx_deleted_eventti_eventid_adultpr                                      | eventtickets_idx_deleted_eventti_eventid_adultpr | 91       | const,const,db.E.eventId       |    24 |   100.00 | Using index                                                                       |
|  3 | DEPENDENT SUBQUERY | EI3             | NULL       | ref         | eventId_2,eventId,userId,eventintresteds_idx_userid_eventid                                                        | eventId_2                                        | 8        |db.E.eventId                   |   108 |     0.84 | Using where; Using index                                                          |
|  2 | DEPENDENT SUBQUERY | eventIntresteds | NULL       | ref         | eventId_2,eventId                                                                                                  | eventId                                          | 8        |db.E.eventId                   |   107 |   100.00 | Using where; Using index                                                          |
+----+--------------------+-----------------+------------+-------------+--------------------------------------------------------------------------------------------------------------------+--------------------------------------------------+----------+----------------------------------------+-------+----------+-----------------------------------------------------------------------------------+

EDIT2:

If anyone could suggest one or more indexes to speed this up, that would be appreciated. This query is generated by ORM (Sequilize), so there is no easy way to just change it manually.


Solution

  • (Please provide SHOW CREATE TABLE for each table.)

    In general JOIN ( SELECT ... ) is less efficient than other techniques.

    Tentatie indexes:

    E:  INDEX(blocked, deleted, approved, privateEvent,
              endDt)     -- last in this index
    orders:  INDEX(paymentStatus, orderStatus, orderType,   -- these first
                   refundId, eventId, shopId)
    invites:  INDEX(userId, deleted, inviteType, eventId)
    UN:  INDEX(notificationType, eventId, userId)
    eventIntresteds:  INDEX(eventId, userId)   -- in this order
    

    Simplification and optimization:

                          ( SELECT  COUNT(*)
                                FROM  eventIntresteds EI1
                                WHERE  EI1.eventId = E.eventId
                                  AND  EI1.userId = 2162 ) > 0 ) THEN '1' ELSE '0' END 
    

    -->

                         EXISTS ( SELECT 1
                                FROM eventIntresteds EI1
                                WHERE  EI1.eventId = E.eventId
                                  AND  EI1.userId = 2162 )
    

    There are several CASE clauses, some may work better by using EXISTS.