简体   繁体   中英

Improving Performance on 2 Left Joins and multiple SUMs in MySQL

I'm no MySQL expert as it probably shows from my query below, but I've managed to get my query to give me the results i want, but it seems very slow to me:

SELECT 
    bouts.bout_id,
    bouts.bout_date,
    bouts.winner,
    bouts.result,
    bouts.ended_in_round,
    bouts.total_rounds,
    bouts.verified AS verified,
    CONCAT(opponent.first_name, ' ', opponent.last_name) AS opponent_name,
    opponent.last_name AS opponent_lastname,
    opponent.id AS opponent_id,
    IF(opponent.id = bouts.boxer_1, bouts.boxer_1_weight, bouts.boxer_2_weight) AS opp_weight,
    IF(opponent.id = bouts.boxer_1, bouts.boxer_2_weight, bouts.boxer_1_weight) AS fighter_weight,
    SUM(opp_bouts.ended_in_round) AS opp_total_rounds_boxed,
    SUM(IF(opp_bouts.winner = -1,1,0)) AS opp_draws,
    SUM(IF(opp_bouts.winner = opponent.id,1,0)) AS opp_wins,
    COUNT(opp_bouts.bout_id) AS opp_fights,
    IF(opponent.id = bouts.boxer_1, bouts.bxr2_power_points, bouts.bxr1_power_points) AS boxer_power,
    IF(opponent.id = bouts.boxer_1, bouts.bxr2_chin_points, bouts.bxr1_chin_points) AS boxer_chin,
    IF(opponent.id = bouts.boxer_1, bouts.bxr2_boxer_points, bouts.bxr1_boxer_points) AS boxer_boxer,
    SUM(IF(opponent.id = opp_bouts.boxer_1, opp_bouts.bxr1_power_points, opp_bouts.bxr2_power_points)) AS opp_power,
    SUM(IF(opponent.id = opp_bouts.boxer_1, opp_bouts.bxr1_chin_points, opp_bouts.bxr2_chin_points)) AS opp_chin,
    SUM(IF(opponent.id = opp_bouts.boxer_1, opp_bouts.bxr1_boxer_points, opp_bouts.bxr2_boxer_points)) AS opp_boxer,
    SUM(IF(opponent.id = opp_bouts.boxer_1, opp_bouts.bxr1_exp_points, opp_bouts.bxr2_exp_points)) AS opp_experience,
    MAX(IF(opp_bouts.verified = 1, opp_bouts.bout_date, 0)) AS verified_up_to,
    MAX(opp_bouts.bout_date) AS opp_last_bout
FROM
    bouts
    LEFT JOIN boxers opponent ON opponent.id=IF(bouts.boxer_2 = '" . $boxer_id . "', bouts.boxer_1, bouts.boxer_2)
    LEFT JOIN bouts opp_bouts ON (opp_bouts.boxer_1=opponent.id OR opp_bouts.boxer_2=opponent.id) AND opp_bouts.bout_date < bouts.bout_date
WHERE bouts.bout_date < '" . $to_date . "' AND (bouts.boxer_1 = '" . $boxer_id . "' OR bouts.boxer_2 = '" . $boxer_id . "')
GROUP BY bouts.bout_id
ORDER BY bouts.bout_date DESC

bouts has around 11,570 rows, boxers has around 86,370 rows, and it's currently taking between 0.6500 - 0.7500 seconds to run. I have a primary key on boxers.id and bouts.bout_id , and indexes on bouts.boxer_1 and bouts.boxer_2 , but my knowledge of using indexes isn't too good either.

Is it normal for the query to be this slow, and is there anything i can do to help speed it up a little?

UPDATE

Explain result of the query. You'll have to let me know if this isn't correct...

MySQL解释输出

UPDATE 2

Bout table structure:

在此处输入图片说明

Boxer table structure:

在此处输入图片说明

UPDATE 3

Ok, some progress maybe...

If i change

LEFT JOIN bouts opp_bouts ON (opp_bouts.boxer_1=opponent.id OR opp_bouts.boxer_2=opponent.id) AND opp_bouts.bout_date < bouts.bout_date

to:

LEFT JOIN bouts opp_bouts ON (opp_bouts.boxer_1=opponent.id) AND opp_bouts.bout_date < bouts.bout_date

It speeds the query up to around 0.0250, and this seems to be a big speed issue. Could i improve the OR function or do it in a different way maybe?

UPDATE 4

Thanks to Kickstart i now have the below code which is soo much faster but I'm not 100% I've done everything correctly... or if i should have indexes on some columns to speed it up further? I'm not sure how it'll scale up yet wither, but it might be the best I'll get?

bouts now has ~46,066 rows and boxers ~90,531, the query runs at 0.0466 with index on bout_date and both boxer_1/2.

SELECT bout_id, 
        bout_date,
        winner,
        result,
        ended_in_round,
        total_rounds,
        verified,
        opponent_first_name,
        opponent_last_name,
        opponent_id,
        opp_weight,
        fighter_weight,
        SUM(opp_ended_in_round) AS opp_total_rounds_boxed,
        SUM(IF(opp_winner = -1,1,0)) AS opp_draws,
        SUM(IF(opp_winner = opponent_id,1,0)) AS opp_wins,
        COUNT(opp_id) AS opp_fights,
        boxer_power,
        boxer_chin,
        boxer_boxer,
        SUM(opp_power_rating) AS opp_power,
        SUM(opp_chin_rating) AS opp_chin,
        SUM(opp_boxer_rating) AS opp_boxer,
        SUM(opp_exp_rating) AS opp_experience,
        MIN(IF(opp_verified = 1,9999-01-01 , opp_bout_date)) AS verified_up_to,
        MAX(opp_bout_date) AS opp_last_bout
    FROM (

    SELECT bouts.bout_id, bouts.total_rounds, bouts.result, bouts.verified, opp_bouts.verified AS opp_verified, opp_bouts.bout_id AS opp_id, bouts.winner, opp_bouts.winner AS opp_winner, bouts.ended_in_round, opp_bouts.ended_in_round AS opp_ended_in_round, bouts.boxer_2_weight AS opp_weight, bouts.bxr1_power_points AS boxer_power, opp_bouts.bxr1_power_points AS opp_power_rating , opp_bouts.bxr1_chin_points AS opp_chin_rating, opp_bouts.bxr1_boxer_points AS opp_boxer_rating, opp_bouts.bxr1_exp_points AS opp_exp_rating, bouts.bxr1_chin_points AS boxer_chin, bouts.bxr1_boxer_points AS boxer_boxer, bouts.boxer_1_weight AS fighter_weight, bouts.boxer_2 AS opponent_id, bouts.bout_date, opp_bouts.bout_date AS opp_bout_date, opponent.first_name AS opponent_first_name, opponent.last_name AS opponent_last_name
    FROM bouts
    LEFT JOIN boxers opponent ON opponent.id = boxer_2
    LEFT JOIN bouts opp_bouts ON opp_bouts.boxer_1 = opponent.id AND opp_bouts.bout_date < bouts.bout_date
    WHERE bouts.bout_date < 'NOW()'
    AND bouts.boxer_1 = '63514'

    UNION ALL

    SELECT bouts.bout_id, bouts.total_rounds, bouts.result, bouts.verified, opp_bouts.verified AS opp_verified, opp_bouts.bout_id AS opp_id, bouts.winner, opp_bouts.winner AS opp_winner, bouts.ended_in_round, opp_bouts.ended_in_round AS opp_ended_in_round, bouts.boxer_2_weight AS opp_weight, bouts.bxr1_power_points AS boxer_power, opp_bouts.bxr2_power_points AS opp_power_rating, opp_bouts.bxr2_chin_points AS opp_chin_rating, opp_bouts.bxr2_boxer_points AS opp_boxer_rating, opp_bouts.bxr2_exp_points AS opp_exp_rating, bouts.bxr1_chin_points AS boxer_chin, bouts.bxr1_boxer_points AS boxer_boxer, bouts.boxer_1_weight AS fighter_weight, bouts.boxer_2 AS opponent_id, bouts.bout_date, opp_bouts.bout_date AS opp_bout_date, opponent.first_name AS opponent_first_name, opponent.last_name AS opponent_last_name
    FROM bouts
    LEFT JOIN boxers opponent ON opponent.id = boxer_2
    LEFT JOIN bouts opp_bouts ON opp_bouts.boxer_2 = opponent.id AND opp_bouts.bout_date < bouts.bout_date
    WHERE bouts.bout_date < 'NOW()'
    AND bouts.boxer_1 = '63514'

    UNION ALL

    SELECT bouts.bout_id, bouts.total_rounds, bouts.result, bouts.verified, opp_bouts.verified AS opp_verified, opp_bouts.bout_id AS opp_id, bouts.winner, opp_bouts.winner AS opp_winner, bouts.ended_in_round, opp_bouts.ended_in_round AS opp_ended_in_round, bouts.boxer_1_weight AS opp_weight, bouts.bxr2_power_points AS boxer_power, opp_bouts.bxr1_power_points AS opp_power_rating, opp_bouts.bxr1_chin_points AS opp_chin_rating, opp_bouts.bxr1_boxer_points AS opp_boxer_rating, opp_bouts.bxr1_exp_points AS opp_exp_rating, bouts.bxr2_chin_points AS boxer_chin, bouts.bxr2_boxer_points AS boxer_boxer, bouts.boxer_2_weight AS fighter_weight, bouts.boxer_1 AS opponent_id, bouts.bout_date, opp_bouts.bout_date AS opp_bout_date, opponent.first_name AS opponent_first_name, opponent.last_name AS opponent_last_name
    FROM bouts
    LEFT JOIN boxers opponent ON opponent.id = boxer_1
    LEFT JOIN bouts opp_bouts ON opp_bouts.boxer_1 = opponent.id AND opp_bouts.bout_date < bouts.bout_date
    WHERE bouts.bout_date < 'NOW()'
    AND bouts.boxer_2 = '63514'

    UNION ALL

    SELECT bouts.bout_id, bouts.total_rounds, bouts.result, bouts.verified, opp_bouts.verified AS opp_verified, opp_bouts.bout_id AS opp_id, bouts.winner, opp_bouts.winner AS opp_winner, bouts.ended_in_round, opp_bouts.ended_in_round AS opp_ended_in_round, bouts.boxer_1_weight AS opp_weight, bouts.bxr2_power_points AS boxer_power, opp_bouts.bxr2_power_points AS opp_power_rating, opp_bouts.bxr2_chin_points AS opp_chin_rating, opp_bouts.bxr2_boxer_points AS opp_boxer_rating, opp_bouts.bxr2_exp_points AS opp_exp_rating, bouts.bxr2_chin_points AS boxer_chin, bouts.bxr2_boxer_points AS boxer_boxer, bouts.boxer_2_weight AS fighter_weight, bouts.boxer_1 AS opponent_id, bouts.bout_date, opp_bouts.bout_date AS opp_bout_date, opponent.first_name AS opponent_first_name, opponent.last_name AS opponent_last_name
    FROM bouts
    LEFT JOIN boxers opponent ON opponent.id = boxer_1
    LEFT JOIN bouts opp_bouts ON opp_bouts.boxer_2 = opponent.id AND opp_bouts.bout_date < bouts.bout_date
    WHERE bouts.bout_date < 'NOW()'
    AND bouts.boxer_2 = '63514'
) AS my_table GROUP BY bout_id ORDER BY bout_date DESC

Using IF to decide on just about all the columns whether you are referring to the first or 2nd boxer will likely slow things down.

Possibly best to have 2 subselects (one for each of boxer_1 and boxer_2), union the results together and then to the SUMs and COUNTs on the results of that.

EDIT. Brief example (not full as I don't fully understand where all the data is coming from). Have 4 queries unioned together (1 for each of boxer_1 and boxer_2 on bouts, and again one of each for those on the 2nd join on bouts). Use the results of this unioned query to do you actual calculations.

SELECT SomeFields,
SUM(SomeField),
etc
FROM
(
    SELECT bouts.boxer_1 AS boxer, bouts.boxer_2 AS opponent, opp_bouts.boxer_2 AS opponentsPrevOpponent
    FROM bouts
    LEFT JOIN boxers opponent ON opponent.id = bouts.boxer_2
    LEFT JOIN bouts opp_bouts ON opp_bouts.boxer_1 = opponent.id AND opp_bouts.bout_date < bouts.bout_date
    WHERE bouts.bout_date < '" . $to_date . "' 
    AND bouts.boxer_1 = '" . $boxer_id . "' 
    UNION ALL
    SELECT bouts.boxer_1 AS boxer, bouts.boxer_2 AS opponent, opp_bouts.boxer_1 AS opponentsPrevOpponent
    FROM bouts
    LEFT JOIN boxers opponent ON opponent.id = bouts.boxer_2
    LEFT JOIN bouts opp_bouts ON opp_bouts.boxer_2 = opponent.id AND opp_bouts.bout_date < bouts.bout_date
    WHERE bouts.bout_date < '" . $to_date . "' 
    AND bouts.boxer_1 = '" . $boxer_id . "' 
    UNION ALL
    SELECT bouts.boxer_2 AS boxer, bouts.boxer_1 AS opponent, opp_bouts.boxer_2 AS opponentsPrevOpponent
    FROM bouts
    LEFT JOIN boxers opponent ON opponent.id = bouts.boxer_1
    LEFT JOIN bouts opp_bouts ON opp_bouts.boxer_1 = opponent.id AND opp_bouts.bout_date < bouts.bout_date
    WHERE bouts.bout_date < '" . $to_date . "' 
    AND bouts.boxer_2 = '" . $boxer_id . "' 
    UNION ALL
    SELECT bouts.boxer_2 AS boxer, bouts.boxer_1 AS opponent, opp_bouts.boxer_1 AS opponentsPrevOpponent
    FROM bouts
    LEFT JOIN boxers opponent ON opponent.id = bouts.boxer_1
    LEFT JOIN bouts opp_bouts ON opp_bouts.boxer_2 = opponent.id AND opp_bouts.bout_date < bouts.bout_date
    WHERE bouts.bout_date < '" . $to_date . "' 
    AND bouts.boxer_2 = '" . $boxer_id . "' 
) Sub1

This way you can get the values you need in the subselects rather than using IF statements on the various aggregate functions

For add speed to your SQL query you may use temporary table to do your left join and after to all your select sum and if

create temporary table new_temp_table as select * from Your query

and after :

select (all your select code) from new_temp_table

When you use temporary table this can be helpfull in your case, not do your query in one time but in 2 or more time this can improve the time execution. 2-3 query more simple is faster thant 1 quesry 3 time complexe. After the connection SQL close the temporary table is drop

Example (in french): table temporaire SQL

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM