[MySQL] 祖先の SQL スクリプトのチューニング

このトピックを見たとき、あなたは自分の目を信じることができますか? 誰かが祖先のコードに触れてみませんか?そうです、その人が私で、今度は動くだけでなくチューニングもしないといけないのです(心の中は無力感がいっぱいで、本当に仕方がありません)。しかし、このチューニングは実際には非常に古典的なものなので、あなたの役に立つことを願って整理して送信しました。

このチューニングの難しさ:

  1. 今回はスクリプトが長すぎて、専門家がほぼすべてのビジネス ロジックを SQL に記述したかどうかはわかりません。
  2. 台本は3人のマスターによって3回調整されていることがわかりますが、うまく調整されていません。あとで、スクリプトが「ログイン」時と「非ログイン」時に2つの分岐処理になることを知りました。

まず、下の図に示すように、「非ログイン」状態でのインターフェースの応答時間を見てください。
image1.png
上の図に示すように、インターフェースは「非ログイン」状態で 1.76 秒かかります。説明する必要があるのは、7.83 秒がトランザクション操作全体の応答結果であることを図が示していることです (多くのリアルタイムの統計と計算が含まれており、その時点では計算とコード ロジックの最適化は行われていませんでした)。 ... 実際、私はそれをぶっきらぼうに最適化するつもりはないので、全体のトランザクションには長い時間がかかります)、写真で言及されているインターフェースとこの記事で言及されているインターフェースは同じインターフェースではなく、問題のあるインターフェースは 1.76 秒かかります調べた結果ですので、この記事の写真は性能を視覚的に見るためのものです結果は対応するインターフェースの実際の実行時間ではありません(実際には単に「怠け者」の文章です、ログを書きたくありませんデータベースの実行時間を表示します...) .

ログインしてからクエリを実行すると、次の図に示すように、パフォーマンスが急激に低下します:
写真2.png
最後に変更した専門家に尋ねたところ、Java レベルで既に最適化されていることがわかりました。 、最適化を続ける場所がありません。したがって、このチューニングでは主に SQL クエリの最適化に焦点を当て、ログイン後のクエリ ステートメントを最初に確認します。実行された SQL スクリプトは次のとおりです。

SELECT *
FROM
    (SELECT 
        p.procurement_id,
            p.display_type,
            p.publish_type,
            p.valid_time,
            p.pay_type,
            p.cust_id,
            p.add_user,
            t.trade_name,
            p.add_time,
            p.oper_user,
            p.oper_time,
            p.platform_audit_status,
            p.platform_back_reason,
            p.platform_audit_user,
            p.platform_audit_time,
            p.status,
            p.procurement_title,
            p.alive_flag,
            c.is_gsp,
            c.is_gmp,
            c.customer_service_user,
            IFNULL(IF(p.display_type = 2, sui.CONTACT_NAME, fc.CONTACT_NAME), '暂无') AS CONTACT_NAME,
            IFNULL(IF(p.display_type = 2, sui.CELLPHONE, fc.cell_phone), '暂无') AS cellphone,
            IF(fc.SEX = 1, '先生', '女士') AS sex,
            IF(INSTR(GROUP_CONCAT(t.TRADE_PUBLISH_STATE), '0') > 0, 0, 1) AS TRADE_PUBLISH_STATE,
            IF(p.display_type = 2, '*******', c.CUST_NAME) AS CUST_NAME,
            SUM(IF((SELECT 
                    COUNT(0)
                FROM
                    spot_procurement_details spd
                WHERE
                    FIND_IN_SET(spd.trade_name_id, '35,65,124,1145,1168,255,288,81,')
                        AND spd.procurement_detail_id = pd.procurement_detail_id) > 0, 1, 0)) AS flag,
            pn.status AS inviteStatus,
            pn.invitation_id,
            pn.send_time,
            (SELECT IF(p.valid_time >= DATE_FORMAT(NOW(), '%Y-%m-%d'), 1, 2)) AS info_status,
            IF(p.status = 1, 1, IF(p.status = 6, 1.5, 2)) AS proc_status,
            p.top_type AS topType,
            p.top_time AS topTime
    FROM spot_procurement p
    LEFT JOIN spot_procurement_invitation pn ON pn.procurement_id = p.procurement_id
    LEFT JOIN spot_procurement_details pd ON pd.procurement_id = p.procurement_id
    LEFT JOIN spot_trade_name t ON t.trade_name_id = pd.trade_name_id
    LEFT JOIN spot_frequent_contacts fc ON p.cust_id = fc.CUST_ID AND fc.ALIVE_FLAG = 1 AND fc.IS_FREQUENT = 1
    LEFT JOIN spot_company c ON c.cust_id = p.cust_id
    LEFT JOIN spot_user_info sui ON c.CUSTOMER_SERVICE_USER = sui.USER_ID
    WHERE p.platform_audit_status = 1 AND p.alive_flag = 1 AND p.status >= 1
            AND (pd.is_split IS NULL OR pd.is_split != 'Y')
            AND (pn.receive_cust_id = '100000000000365' OR p.publish_type = 2)
            AND p.top_Type IN (1 , '3')
    GROUP BY p.procurement_id UNION (SELECT 
        p.procurement_id,
            p.display_type,
            p.publish_type,
            p.valid_time,
            p.pay_type,
            p.cust_id,
            p.add_user,
            t.trade_name,
            p.add_time,
            p.oper_user,
            p.oper_time,
            p.platform_audit_status,
            p.platform_back_reason,
            p.platform_audit_user,
            p.platform_audit_time,
            p.status,
            p.procurement_title,
            p.alive_flag,
            c.is_gsp,
            c.is_gmp,
            c.customer_service_user,
            IFNULL(IF(p.display_type = 2, sui.CONTACT_NAME, fc.CONTACT_NAME), '暂无') AS CONTACT_NAME,
            IFNULL(IF(p.display_type = 2, sui.CELLPHONE, fc.cell_phone), '暂无') AS cellphone,
            IF(fc.SEX = 1, '先生', '女士') AS sex,
            IF(INSTR(GROUP_CONCAT(t.TRADE_PUBLISH_STATE), '0') > 0, 0, 1) AS TRADE_PUBLISH_STATE,
            IF(p.display_type = 2, '*******', c.CUST_NAME) AS CUST_NAME,
            SUM(IF((SELECT COUNT(0)
                FROM spot_procurement_details spd
                WHERE FIND_IN_SET(spd.trade_name_id, '35,65,124,1145,1168,255,288,81,')
                        AND spd.procurement_detail_id = pd.procurement_detail_id) > 0, 1, 0)) AS flag,
            pn.status AS inviteStatus,
            pn.invitation_id,
            pn.send_time,
            (SELECT IF(p.valid_time >= DATE_FORMAT(NOW(), '%Y-%m-%d'), 1, 2)) AS info_status,
            IF(p.status = 1, 1, IF(p.status = 6, 1.5, 2)) AS proc_status,
            p.top_type AS topType,
            p.top_time AS topTime
    FROM spot_procurement p
    LEFT JOIN spot_procurement_invitation pn ON pn.procurement_id = p.procurement_id
    LEFT JOIN spot_procurement_details pd ON pd.procurement_id = p.procurement_id
    LEFT JOIN spot_trade_name t ON t.trade_name_id = pd.trade_name_id
    LEFT JOIN spot_frequent_contacts fc ON p.cust_id = fc.CUST_ID AND fc.ALIVE_FLAG = 1 AND fc.IS_FREQUENT = 1
    LEFT JOIN spot_company c ON c.cust_id = p.cust_id
    LEFT JOIN spot_user_info sui ON c.CUSTOMER_SERVICE_USER = sui.USER_ID
    WHERE
        p.platform_audit_status = 1 AND p.alive_flag = 1 AND p.status >= 1
            AND (pd.is_split IS NULL OR pd.is_split != 'Y')
            AND (pn.receive_cust_id = '100000000000365' OR p.publish_type = 2)
    GROUP BY p.procurement_id)) sss
WHERE sss.TRADE_PUBLISH_STATE = 1
ORDER BY sss.info_status ASC , sss.add_time DESC
LIMIT 0 , 10

この 107 行の浅いスクリプト...逆アセンブル分析の結果、UNION キーワードを介してスクリプトを 2 つの部分に分解できることがわかりました
写真3.png
.2.29 秒かかりました.
次に、ネストされたクエリの内部スクリプトを 2 つの部分に分解します。各部分は Explain を通じて実行結果を分析します。まず、下の図に示すように、最初の部分を見てください。 pn テーブルと pd テーブルは写真4.png
異常です。他のテーブルの接続は比較的正常で、少なくともインデックスにアクセスできます (key と key_len はインデックスの名前と長さを示します)。次に、pn と pd に対応する Extra 列が示す内容を確認すると、返される内容は「レコードごとにチェックされた範囲 (インデックス マップ: 0x2)」です。

「レコードごとに範囲をチェックする」は以前他のチューニング共有でも言及されていましたが、現在のテーブルの接続フィールドには possible_key フィールドがありますが、MySQL 実行アナライザーは実行中に "何らかの" 理由でインデックスを使用しません (上の図から、テーブル pn と pd の両方に possible_key がありますが、key と key_len が両方とも null であり、インデックスが作成されていないことがわかります。) したがって、Range checked のプロンプトが表示され、接続内の各レコードがチェックする必要があります。したがって、このエラーは MySQL で最も遅いエラー メッセージの 1 つでもあります。

インデックスをとっていないので、なぜインデックスをとらないのかを調べる必要があります。pn テーブルと pd テーブルの接続は次のとおりです。

FROM spot_procurement p
LEFT JOIN spot_procurement_invitation pn ON pn.procurement_id = p.procurement_id
LEFT JOIN spot_procurement_details pd ON pd.procurement_id = p.procurement_id

実際には、2 つのテーブルはテーブル p の正しい接続であり、procurement_id フィールドを介して接続されています. purchase_id フィールドは p テーブルの主キーであり、pn テーブルと pd テーブルの purchase_id フィールドはそれらの外部データです問題はないはずです。ただし、p、pn、pd の 3 つのテーブルを比較すると、p テーブルの purchase_id フィールドは bigint のデータ型であるのに対し、pn テーブルと pd テーブルの purchase_id のデータ型は varchar 型であることがわかります。したがって、説明でインデックスを使用しない理由は、一貫性のないデータ型が原因である可能性が非常に高いです

(一貫性のないデータ型によって引き起こされる別のパフォーマンスの問題) .

フィールドのデータ型に一貫性がないため、関連付けを行う前に、外部テーブルのフィールドを内部テーブル フィールドの対応するデータ型に暗黙的に変換する必要があります。このプロセスでは、次のステートメントに相当します。

FROM spot_procurement p
LEFT JOIN spot_procurement_invitation pn ON CAST(pn.procurement_id AS UNSIGNED integer) = p.procurement_id
LEFT JOIN spot_procurement_details pd ON CAST(pd.procurement_id AS UNSIGNED integer) = p.procurement_id

その他の問題はここで確認できます。pn と pd は外部テーブルとして = の前に配置され、外部フィールドは CAST 関数を使用してフィールドの型変換を実行する必要があるため、このフィールドはインデックスを使用しません。

したがって、元のロジックを変更せずに、次のように変更します。

SELECT 
        p.procurement_id,
            p.display_type,
            p.publish_type,
            p.valid_time,
            p.pay_type,
            p.cust_id,
            p.add_user,
            t.trade_name,
            p.add_time,
            p.oper_user,
            p.oper_time,
            p.platform_audit_status,
            p.platform_back_reason,
            p.platform_audit_user,
            p.platform_audit_time,
            p.status,
            p.procurement_title,
            p.alive_flag,
            c.is_gsp,
            c.is_gmp,
            c.customer_service_user,
            IFNULL(IF(p.display_type = 2, sui.CONTACT_NAME, fc.CONTACT_NAME), '暂无') AS CONTACT_NAME,
            IFNULL(IF(p.display_type = 2, sui.CELLPHONE, fc.cell_phone), '暂无') AS cellphone,
            IF(fc.SEX = 1, '先生', '女士') AS sex,
            IF(INSTR(GROUP_CONCAT(t.TRADE_PUBLISH_STATE), '0') > 0, 0, 1) AS TRADE_PUBLISH_STATE,
            IF(p.display_type = 2, '*******', c.CUST_NAME) AS CUST_NAME,
            SUM(IF((SELECT COUNT(0)
                FROM spot_procurement_details spd
                WHERE FIND_IN_SET(spd.trade_name_id, '35,65,124,1145,1168,255,288,81')
                        AND spd.procurement_detail_id = pd.procurement_detail_id) > 0, 1, 0)) AS flag,
            pn.status AS inviteStatus,
            pn.invitation_id,
            pn.send_time,
            (SELECT IF(p.valid_time >= DATE_FORMAT(NOW(), '%Y-%m-%d'), 1, 2)) AS info_status,
            IF(p.status = 1, 1, IF(p.status = 6, 1.5, 2)) AS proc_status,
            p.top_type AS topType,
            p.top_time AS topTime
    FROM spot_procurement p
    LEFT JOIN 
    (select a.receive_cust_id,a.status,a.invitation_id,a.send_time, CAST(a.procurement_id AS UNSIGNED integer) as procurement_id from spot_procurement_invitation a) pn ON pn.procurement_id = p.procurement_id
    LEFT JOIN 
    (select b.procurement_detail_id,CAST(b.procurement_id AS UNSIGNED integer) as procurement_id,b.trade_name_id,b.is_split from spot_procurement_details b ) pd ON pd.procurement_id = p.procurement_id
    LEFT JOIN spot_trade_name t ON t.trade_name_id = pd.trade_name_id
    LEFT JOIN spot_frequent_contacts fc ON p.cust_id = fc.CUST_ID AND fc.ALIVE_FLAG = 1 AND fc.IS_FREQUENT = 1
    LEFT JOIN spot_company c ON c.cust_id = p.cust_id
    LEFT JOIN spot_user_info sui ON c.CUSTOMER_SERVICE_USER = sui.USER_ID
    WHERE p.platform_audit_status = 1 AND (pd.is_split IS NULL OR pd.is_split != 'Y')
            AND p.alive_flag = 1 AND p.status >= 1
            AND (pn.receive_cust_id = '100000000000365' OR p.publish_type = 2)
            AND p.top_Type IN (1 , '3')
    GROUP BY p.procurement_id

ここでは、まず変換が必要なフィールドを明示的に変換してから、結合接続を行います. 説明後、実行プランは次のとおりです: auto_key1 バンドは、接続時に元の null を置き換えるために使用され、2 つのテーブル a と
写真5.png
bエスケープのみに使用されるため、完全なテーブル スキャンです。ただし、チェックした範囲が Extra 列に存在しなくなっていることに注意してください。

次に、第2部の文を見てください.比較すると、基本的に第1部の文と似ているため、同じ最適化方法を使用してSQLを最適化できます.最適化された全体的なExplain実行計画は図のようになります.以下: 上の図に示すように、
写真6.png
いいえ 他の特別なケースでは、下の図に示すように、直接実行してクエリの効果を確認します:
写真7.png
SQL を変更した後、下の図に示すように、インターフェイスの読み込み速度を確認します:
写真8.png
アカウントログイン状態で、インターフェースが5.42秒から0.82秒に増加し、実行効率が81.5%増加しました。

おすすめ

転載: blog.csdn.net/kida_yuan/article/details/129945970