UNION est lent mais les deux requêtes sont rapides et séparées

11

Je ne sais pas quoi faire d'autre à propos de celui-ci. J'ai une table qui a des colonnes start et stop et je veux en renvoyer les résultats joints à la fois par start et par stop et je veux une distinction claire entre les deux. Maintenant, les deux requêtes s'exécutent rapidement séparément:

SELECT
            UNIX_TIMESTAMP(CONVERT_TZ(start_dev, '+00:00', GetCarrierTimezone(a0.carrier_id))) AS alertStart,
            NULL AS alertStop,
            c0.name AS carrier_name,
            carrier_image,
            l0.Latitude,
            l0.Longitude
        FROM
            carriers AS c0
                INNER JOIN start_stop AS a0 ON a0.carrier_id = c0.id
                    INNER JOIN pcoarg AS l0 ON a0.startLogId = l0.id
        WHERE
                FIND_IN_SET(a0.carrier_id, '89467,1,64578,222625,45013') > 0
            AND
                start_dev > '2013-03-11 11:46:48'
            AND 
                start_dev = (SELECT MIN(start_dev) FROM start_stop AS a1 WHERE a0.carrier_id = a1.carrier_id AND DATE(a1.start_dev) = DATE(a0.start_dev))
        AND IsNotificationInSchedule(22, start_dev) > 0

Donc, celui-ci prend 0,063. Mais si je le combine en UNION (peu importe que ce soit UNION ALL OU DISTINCT OU QUELQUE CHOSE), cela ne prend que 0,400 secondes.

SELECT * FROM
(
    (
        SELECT
            UNIX_TIMESTAMP(CONVERT_TZ(start_dev, '+00:00', GetCarrierTimezone(a0.carrier_id))) AS alertStart,
            NULL AS alertStop,
            c0.name AS carrier_name,
            carrier_image,
            l0.Latitude,
            l0.Longitude
        FROM
            carriers AS c0
                INNER JOIN start_stop AS a0 ON a0.carrier_id = c0.id
                    INNER JOIN pcoarg AS l0 ON a0.startLogId = l0.id
        WHERE
                FIND_IN_SET(a0.carrier_id, '89467,1,64578,222625,45013') > 0
            AND
                start_dev > '2013-03-11 11:46:48'
            AND 
                start_dev = (SELECT MIN(start_dev) FROM start_stop AS a1 WHERE a0.carrier_id = a1.carrier_id AND DATE(a1.start_dev) = DATE(a0.start_dev))
            AND IsNotificationInSchedule(22, start_dev) > 0
    ) UNION ALL (
        SELECT
            NULL AS alertStart,
            UNIX_TIMESTAMP(CONVERT_TZ(stop_dev, '+00:00', GetCarrierTimezone(a0.carrier_id))) AS alertStop,
            c0.name AS carrier_name,
            carrier_image,
            l0.Latitude,
            l0.Longitude
        FROM
            start_stop AS a0
                INNER JOIN carriers AS c0 ON a0.carrier_id = c0.id
                    INNER JOIN pcoarg AS l0 ON a0.stopLogId = l0.id
        WHERE
                FIND_IN_SET(a0.carrier_id, '89467,1,64578,222625,45013') > 0
            AND
                stop_dev > '2013-03-11 11:46:48'
            AND 
                stop_dev = (SELECT MAX(stop_dev) FROM start_stop AS a1 WHERE a0.carrier_id = a1.carrier_id AND DATE(a1.stop_dev) = DATE(a0.stop_dev))
            AND IsNotificationInSchedule(22, start_dev) > 0
    )
) AS startStops
ORDER BY IF(alertStart IS NULL, alertStop, alertStart)

Voici EXPLAIN sur une seule requête:

1   PRIMARY c0  ALL PRIMARY             17  Using where
1   PRIMARY a0  ref PRIMARY,startstop_carriers_stopdev_idx,georefidx,startstop_carriers_startdev_idx    startstop_carriers_stopdev_idx  4   test_backoffice.c0.id   72  Using where
1   PRIMARY l0  ref id ASC  id ASC  4   test_backoffice.a0.startLogId   1   Using where
2   DEPENDENT SUBQUERY  a1  ref PRIMARY,startstop_carriers_stopdev_idx,georefidx,startstop_carriers_startdev_idx    startstop_carriers_stopdev_idx  4   test_backoffice.a0.carrier_id   72  Using where; Using index

Et voici l'EXPLIQUER pour le JOIN:

1   PRIMARY <derived2>  system                  0   const row not found
2   DERIVED c0  ALL PRIMARY             17  Using where
2   DERIVED a0  ref PRIMARY,startstop_carriers_stopdev_idx,georefidx,startstop_carriers_startdev_idx    startstop_carriers_stopdev_idx  4   test_backoffice.c0.id   72  Using where
2   DERIVED l0  ref id ASC  id ASC  4   test_backoffice.a0.startLogId   1   Using where
3   DEPENDENT SUBQUERY  a1  ref PRIMARY,startstop_carriers_stopdev_idx,georefidx,startstop_carriers_startdev_idx    startstop_carriers_stopdev_idx  4   test_backoffice.a0.carrier_id   72  Using where; Using index
4   UNION   c0  ALL PRIMARY             17  Using where
4   UNION   a0  ref PRIMARY,startstop_carriers_stopdev_idx,georefidx,startstop_carriers_startdev_idx    startstop_carriers_stopdev_idx  4   test_backoffice.c0.id   72  Using where
4   UNION   l0  ref id ASC  id ASC  4   test_backoffice.a0.stopLogId    1   Using where
5   DEPENDENT SUBQUERY  a1  ref PRIMARY,startstop_carriers_stopdev_idx,georefidx,startstop_carriers_startdev_idx    startstop_carriers_stopdev_idx  4   test_backoffice.a0.carrier_id   72  Using where; Using index
    UNION RESULT    <union2,4>  ALL                     

De l'aide sur celui-ci serait grandement appréciée. :)

ÉDITER:

J'obtiens un résultat incohérent. Si je supprime le convert_tz par exemple et essaie d'obtenir le fuseau horaire en dehors de l'union, j'obtiens des résultats très rapides, mais si je renomme le résultat, il revient automatiquement à la même requête underperformante:

SELECT
    *,
    GetCarrierTimezone(carrier_id) timezone
FROM
(

cela prend 0,374 s

SELECT
    *,
    GetCarrierTimezone(carrier_id)
FROM
(

alors que cela prend 0,078 (principalement le décalage de la base de données vers ma machine) ..

helderjsm
la source
Le plus simple serait de les exécuter séparément et de combiner les résultats dans l'application.
ypercubeᵀᴹ
salut @ypercube, cela m'est venu à l'esprit :) mais c'est si moche de faire ça et de maintenir ce code. En plus, je dois encore trier les résultats en php.
helderjsm
Je voulais dire exécuter les 2 requêtes avec le tri souhaité. Il vous suffit ensuite de fusionner en php (pas de tri).
ypercubeᵀᴹ
1
Le tri n'est pas linéaire. Le résultat de la requête 1 peut être compris entre les résultats de la requête 2.
helderjsm
1
Je ne pense pas que @ypercube suppose que les résultats ne se chevauchent pas: une 'fusion' est beaucoup moins cher / plus facile qu'une sorte à implémenter en php. Bien sûr, résoudre le problème dans le SQL si possible serait une bien meilleure solution :)
Jack dit essayez topanswers.xyz

Réponses:

1

Je m'attendrais à ce que cela se produise en raison de la COMMANDE PAR que vous avez là-dedans.

Essayez ceci dans la première partie de l'UNION:

SELECT
            UNIX_TIMESTAMP(CONVERT_TZ(start_dev, '+00:00', GetCarrierTimezone(a0.carrier_id))) AS alertFoo,
            /* NULL AS alertStop, */

Et ceci dans la deuxième partie:

SELECT
            /* NULL AS alertStart, */
            UNIX_TIMESTAMP(CONVERT_TZ(stop_dev, '+00:00', GetCarrierTimezone(a0.carrier_id))) AS alertFoo,

Et puis remplacez le ORDER BYpar

ORDER BY alertFoo

En d'autres termes, supprimez le besoin du CI dans l'ordre de.

Thomas Kejser
la source
Salut Thomas, Tout d'abord merci pour votre replay. Comme je l'ai dit dans un post précédent, cela a été corrigé il y a quelque temps. Le truc, c'est que j'avais besoin de faire la distinction entre l'alerte 1 et l'alerte 2. En tout cas, l'ordre est fait sur le résultat des jointures et non sur la jointure elle-même. Il n'y avait pas tant de résultats pour justifier la lenteur de la requête.
helderjsm
0

Dans un cas très très similaire, j'ai remarqué à partir de la liste des processus de mysql le très mauvais comportement de «copier dans la table temporaire» (copier quoi? Je ne sais pas). Je pense que mysql a tenté une «meilleure approche» pour les requêtes, mais dans ce cas, a échoué, donc utiliser du code pour «fusionner» les résultats de 2 requêtes a bien fonctionné.

realtebo
la source
Salut realtebo, Merci pour la contribution. C'est un peu vieux maintenant, mais pour ce que je me souviens, l'incohérence était parce que certains comment mysql mettait en cache certains résultats et pas d'autres. J'ai finalement recréé la requête d'une manière plus efficace, spécialement en gardant une trace des valeurs dont j'avais besoin dans une table séparée, ce qui rend les indices plus efficaces.
helderjsm
0

La raison principale du ralentissement de l'union sql est qu'une union fait que mysqld crée une table temporaire interne. Il crée juste une table pour UNION ALL et une table avec un index (pour supprimer les doublons) pour UNION DISTINCT.

J'espère que cela t'aides.

salut
la source