[Solved] How can I optimize my MySQL query? as it is taking 45 seconds to fetch records
Looking to automatically optimize YOUR SQL query? Start for free.

EverSQL Database Performance Knowledge Base

How can I optimize my MySQL query? as it is taking 45 seconds to fetch records

Database type:

My database table "ringtune_history" has 40K records and "ringtunes" has 4K records and the query is taking too much time to process.

SELECT  `ringtunes`.*,
        count(case when ringtune_history.Action = 'Download' then ringtune_history.Action end) as Org_Downloads,
        count(case when ringtune_history.Action = 'View' then ringtune_history.Action end) as Org_Views,
        count(case when ringtune_history.Action = 'Play' then ringtune_history.Action end) as Total_Plays,
        count(case when ringtune_history.Action = 'Like' then ringtune_history.Action end) as Total_Likes,
        `categories`.`Name` as `Category_Name`
    FROM  `ringtunes`
    LEFT JOIN  `ringtune_history`  ON `ringtune_history`.`Ringtune_Id` = `ringtunes`.`Id`
    LEFT JOIN  `categories`  ON `categories`.`Id` = `ringtunes`.`Category`
    WHERE  `ringtunes`.`Deleted` = 0
      AND  `ringtunes`.`Status` = 1
      AND  `categories`.`Deleted` = 0
      AND  `categories`.`Status` = 1
    GROUP BY  `ringtunes`.`Id`
    ORDER BY  `ringtunes`.`Id` DESC
    LIMIT  20

How to optimize this SQL query?

The following recommendations will help you in your SQL tuning process.
You'll find 3 sections below:

  1. Description of the steps you can take to speed up the query.
  2. The optimal indexes for this query, which you can copy and create in your database.
  3. An automatically re-written query you can copy and execute in your database.
The optimization process and recommendations:
  1. Avoid Selecting Unnecessary Columns (query line: 33): Avoid selecting all columns with the '*' wildcard, unless you intend to use them all. Selecting redundant columns may result in unnecessary performance degradation.
  2. Create Optimal Indexes (modified query below): The recommended indexes are an integral part of this optimization effort and should be created before testing the execution duration of the optimized query.
  3. Prefer Inner Join Over Left Join (modified query below): We identified that one or more left joined entities (e.g. `categories`) are used in the 'where' clause, in a way that allows to replace it with an optimized inner join. Inner joins can be fully optimized by the database, while Left joins apply limitations on the database's optimizer.
  4. Reduce Impact Of Subqueries In Select Clause (modified query below): Subqueries in the SELECT clause will be executed once per each select row. Therefore, reducing the amount of selected rows in the FROM clause before getting to the SELECT clause will result in a performance improvement.
  5. Replace Left Join With Subquery (modified query below): The pattern of inflating the amount of data (using joins) and deflating (using GROUP BY) usually slows down queries. In this case, it can be avoided by moving some of the logic to the SELECT clause, and therefore removing some of the LEFT JOINs. In some cases, this transformation can lead to an obsolete GROUP BY clause, which can also be removed.
Optimal indexes for this query:
ALTER TABLE `categories` ADD INDEX `categories_idx_deleted_status_id` (`Deleted`,`Status`,`Id`);
ALTER TABLE `ringtune_history` ADD INDEX `ringtune_history_idx_ringtune_id` (`Ringtune_Id`);
ALTER TABLE `ringtunes` ADD INDEX `ringtunes_idx_deleted_status_id` (`Deleted`,`Status`,`Id`);
ALTER TABLE `ringtunes` ADD INDEX `ringtunes_idx_id` (`Id`);
The optimized query:
SELECT
        optimizedSub1.*,
        (SELECT
            count(CASE 
                WHEN ringtune_history.Action = 'Download' THEN ringtune_history.Action END) AS Org_Downloads 
FROM
`ringtune_history` 
WHERE
`ringtune_history`.`Ringtune_Id` = optimizedSub1.ringtunes_Id LIMIT 1) AS Org_Downloads,
(SELECT
count(CASE 
    WHEN ringtune_history.Action = 'View' THEN ringtune_history.Action END) AS Org_Views 
FROM
`ringtune_history` 
WHERE
`ringtune_history`.`Ringtune_Id` = optimizedSub1.ringtunes_Id LIMIT 1) AS Org_Views,
(SELECT
count(CASE 
    WHEN ringtune_history.Action = 'Play' THEN ringtune_history.Action END) AS Total_Plays 
FROM
`ringtune_history` 
WHERE
`ringtune_history`.`Ringtune_Id` = optimizedSub1.ringtunes_Id LIMIT 1) AS Total_Plays,
(SELECT
count(CASE 
    WHEN ringtune_history.Action = 'Like' THEN ringtune_history.Action END) AS Total_Likes 
FROM
`ringtune_history` 
WHERE
`ringtune_history`.`Ringtune_Id` = optimizedSub1.ringtunes_Id LIMIT 1) AS Total_Likes 
FROM
(SELECT
`ringtunes`.*,
`categories`.`Name` AS `Category_Name`,
`ringtunes`.`Id` AS ringtunes_Id 
FROM
`ringtunes` 
INNER JOIN
`categories` 
    ON `categories`.`Id` = `ringtunes`.`Category` 
WHERE
`ringtunes`.`Deleted` = 0 
AND `ringtunes`.`Status` = 1 
AND `categories`.`Deleted` = 0 
AND `categories`.`Status` = 1 
GROUP BY
`ringtunes`.`Id` 
ORDER BY
`ringtunes`.`Id` DESC LIMIT 20) AS optimizedSub1

Related Articles



* original question posted on StackOverflow here.