本文介紹了如何對慢SQL進行有效限流。

在數據庫會話或者慢日志中發現大量慢SQL,大量占用數據庫資源,同時活躍會話數、CPU使用率、IOPS、內存使用率等監控指標一項或者多項處于高位。分析后發現這些慢SQL不屬于核心業務,是優化不足的爛SQL,為保障核心業務的穩定運行,此時我們需要對其進行限流。

相關限流語法,請參見SQL限流

SQL限流的運維操作步驟

  1. 實例會話頁面或使用如下語句發現慢SQL。
    select *
      from information_schema.processlist
     where COMMAND!= 'SLEEP'
       and TIME>= 1000
     order by TIME DESC;
  2. 分析慢SQL,請參見如何分析及優化慢SQL
  3. 創建限流規則,可通過SQL命令,或者使用控制臺上實例會話頁面的SQL限流功能。
  4. 從以下幾方面觀察限流規則效果。
    • 監控指標恢復情況;
    • 業務側反饋;
    • show ccl_rules查看每個限流規則的限流情況的統計信息;
    • 查看會話和SQL日志。
  5. 創建索引、修改SQL、增加資源等。
  6. 使用DROP CCL_RULE或者CLEAR CCL_RULES語句關閉限流規則。

如下案例說明了如何對發現的慢SQL進行限流,您可以參照案例中的限流規則,修改后使用。

案例1: 慢SQL屬于同一個SQL模板

某DBA收到了數據庫資源某指標處于高位的報警,查看數據庫慢日志和會話后均發現有如下的慢SQL:

+--------+---------------+---------------------+--------------------+---------+------+-------+----------------------------------------------+-----------------+
| ID     | USER          | HOST                | DB                 | COMMAND | TIME | STATE | INFO                                         | SQL_TEMPLATE_ID |
+--------+---------------+---------------------+--------------------+---------+------+-------+----------------------------------------------+-----------------+
| 951494 | userxxxxxxxxx | 47.100.XX.XX:33830     | analy_db           | Query   |   40 |       | select * from bmsql_oorder where `o_id` > 12 | 65c92c88        |
| 952468 | userxxxxxxxxx | 47.100.XX.XX:33517     | analy_db           | Query   |   43 |       | select * from bmsql_oorder where `o_id` > 10 | 65c92c88        |
| 953468 | userxxxxxxxxx | 47.100.XX.XX:33527     | analy_db           | Query   |   43 |       | select * from bmsql_oorder where `o_id` > 23 | 65c92c88        |
| 954468 | userxxxxxxxxx | 47.100.XX.XX:33537     | analy_db           | Query   |   43 |       | select * from bmsql_oorder where `o_id` > 25 | 65c92c88        |
| 955468 | userxxxxxxxxx | 47.100.XX.XX:33547     | analy_db           | Query   |   43 |       | select * from bmsql_oorder where `o_id` > 27 | 65c92c88        |
+--------+---------------+---------------------+--------------------+---------+------+-------+----------------------------------------------+-----------------+

可見,這些慢SQL屬于同一個SQL模板(模板ID為65c92c88):

select * from bmsql_oorder where `o_id` > ?

bmsql_oorder為一個數據量較大的表,而且列o_id上沒有索引,顯然這是一個未經優化的SQL,占盡了數據庫資源影響了其他重要SQL的正常執行。這是一個非常適合利用模板ID去做SQL限流的場景。

創建限流規則
  • 如果這個SQL模板的SQL不應該在當時執行,而是應該在業務低峰期執行,可以創建SQL限流規則不讓它執行:
    CREATE CCL_RULE `KILL_CCL`       //限流規則名稱為KILL_CCL
        ON `analy_db`.`*`              //&匹配analy_db下的所有表上執行的SQL
      TO 'userxxxxxxxxx'@'%'         //&匹配來自userxxxxxxxxx用戶的SQL
        FOR SELECT                     //&匹配是SELECT類型的SQL語句
      FILTER BY TEMPLATE '65c92c88'  //&匹配模板ID
      WITH MAX_CONCURRENCY = 0;      //設置單節點并發度為0,不允許匹配到的SQL執行

    客戶端再次執行這類SQL的時候將會返回報錯信息:

    ERROR 3009 (HY000): [13172dbaf2801000][10.93.159.222:3029][analy_db]Exceeding the max concurrency 0 per node of ccl rule KILL_CCL
  • 如果允許這個SQL模板的SQL少量執行,只要不占盡數據庫資源就行,可以在創建限流規則的時候設置一定的并發度:
    CREATE CCL_RULE `KILL_CCL_2`       //限流規則名稱為KILL_CCL_2
        ON `analy_db`.`*`              //&匹配analy_db下的所有表上執行的SQL
      TO 'userxxxxxxxxx'@'%'         //&匹配來自userxxxxxxxxx用戶的SQL
        FOR SELECT                     //&匹配是SELECT類型的SQL語句
      FILTER BY TEMPLATE '65c92c88'  //&匹配模板ID
      WITH MAX_CONCURRENCY = 2;      //允許單個節點可以同時有兩個這樣的SQL在執行

    也可使用實例會話頁面里的SQL限流功能,進行如下操作:

    456789
  • 如果希望這個SQL模板的SQL執行的時候可以慢,但盡量不要出錯,則可以設置等待隊列和等待超時時間(默認為600秒):
    CREATE CCL_RULE `QUEUE_CCL_2`       //限流規則名稱為QUEUE_CCL_2
        ON `analy_db`.`*`              //&匹配analy_db下的所有表上執行的SQL
      TO 'userxxxxxxxxx'@'%'         //&匹配來自userxxxxxxxxx用戶的SQL
        FOR SELECT                     //&匹配是SELECT類型的SQL語句
      FILTER BY TEMPLATE '65c92c88'  //&匹配模板ID
      WITH MAX_CONCURRENCY = 2, WAIT_QUEUE_SIZE=20, WAIT_TIMEOUT=500; //單節點并發度為2,單節點等待隊列長度為20,等待超時時間為500秒

    創建完后,可以通過show ccl_rules指令查詢各個限流規則的實際效果,例如當前匹配到某個限流規則的正在執行的SQL數量、被限流引起報錯的SQL數量、總匹配成功次數等。如果想放開被限流SQL,例如在增加了某個索引后,被限流SQL的執行效率變高了,則可以通過drop ccl_rule命令來關閉指定限流規則,或者使用clear ccl_rules來關閉所有的限流規則。

    上面的SQL也可以通過關鍵字來限流,將SQL語句上的關鍵字做拆分,得到如下關鍵字列表:

    • select
    • from
    • bmsql_oorder
    • where
    • `o_id`

    創建限流規則:

    CREATE CCL_RULE `KILL_CCL`       //限流規則名稱為KILL_CCL
        ON `analy_db`.`*`              //&匹配analy_db下的所有表上執行的SQL
      TO 'userxxxxxxxxx'@'%'         //&匹配來自userxxxxxxxxx用戶的SQL
        FOR SELECT                     //&匹配是SELECT類型的SQL語句
      FILTER BY KEYWORD('select','from','bmsql_oorder','where','`o_id`')  //&匹配模板ID
      WITH MAX_CONCURRENCY = 0;      //設置單節點并發度為0,不允許匹配到的SQL執行

    在能獲取到模板ID(在SQL日志、explain命令、會話中)的情況下,還推薦使用更精準的基于模板ID的限流。

    也可使用實例會話頁面里的SQL限流功能,操作如下:

    456789

案例2: 慢SQL都是同一個SQL

某DBA收到了數據庫資源某指標處于高位的報警,查看數據庫慢日志和會話后均發現有如下的慢SQL:

+--------+---------------+---------------------+--------------------+---------+------+-------+---------------------------------------------------+-----------------+
| ID     | USER          | HOST                | DB                 | COMMAND | TIME | STATE | INFO                                              | SQL_TEMPLATE_ID |
+--------+---------------+---------------------+--------------------+---------+------+-------+---------------------------------------------------+-----------------+
| 951494 | userxxxxxxxxx | 47.100.XX.XX:33830     | analy_db           | Query   |   40 |       | select * from bmsql_oorder where o_carrier_id = 2 | 438b00e4        |
| 952468 | userxxxxxxxxx | 47.100.XX.XX:33517     | analy_db           | Query   |   43 |       | select * from bmsql_oorder where o_carrier_id = 2 | 438b00e4        |
| 953468 | userxxxxxxxxx | 47.100.XX.XX:33527     | analy_db           | Query   |   43 |       | select * from bmsql_oorder where o_carrier_id = 2 | 438b00e4        |
| 954468 | userxxxxxxxxx | 47.100.XX.XX:33537     | analy_db           | Query   |   43 |       | select * from bmsql_oorder where o_carrier_id = 2 | 438b00e4        |
| 955468 | userxxxxxxxxx | 47.100.XX.XX:33547     | analy_db           | Query   |   43 |       | select * from bmsql_oorder where o_carrier_id = 2 | 438b00e4        |
+--------+---------------+---------------------+--------------------+---------+------+-------+---------------------------------------------------+-----------------+

bmsql_oorder中的符合o_carrier_id = 2條件的有較多記錄,導致了慢SQL,如果使用模板ID限流,則會影響o_carrier_id不是2的SQL語句,如果使用關鍵字限流則會影響類似如下的正常SQL:

select * from bmsql_oorder where o_carrier_id = 2 limit 1;
select * from bmsql_oorder where o_carrier_id = 2 and o_c_id = 1;

限流具體的SQL,可以使用模板ID加關鍵字的方法,創建如下限流規則:

CREATE CCL_RULE `KILL_CCL`       //限流規則名稱為KILL_CCL
    ON `analy_db`.`*`              //&匹配analy_db下的所有表上執行的SQL
  TO 'userxxxxxxxxx'@'%'         //&匹配來自userxxxxxxxxx用戶的SQL
    FOR SELECT                     //&匹配是SELECT類型的SQL語句
  FILTER BY TEMPLATE '438b00e4'  //&匹配模板ID 438b00e4
  FILTER BY KEYWORD('o_carrier_id','2')  //&匹配參數關鍵字
  WITH MAX_CONCURRENCY = 0;      //設置單節點并發度為0,不允許匹配到的SQL執行

如果使用的PolarDB-X版本為5.4.11以上,且該SQL不在prepare模式下執行,還可以使用如下高階語法進行限流:

CREATE CCL_RULE `KILL_CCL`       //限流規則名稱為KILL_CCL
    ON `analy_db`.`*`              //&匹配analy_db下的所有表上執行的SQL
  TO 'userxxxxxxxxx'@'%'         //&匹配來自userxxxxxxxxx用戶的SQL
    FOR SELECT                     //&匹配是SELECT類型的SQL語句
  FILTER BY QUERY 'select * from bmsql_oorder where o_carrier_id = 2'  //&匹配SQL語句
  WITH MAX_CONCURRENCY = 0;      //設置單節點并發度為0,不允許匹配到的SQL執行

案例3: 慢SQL集包含多個SQL模板

某DBA收到了數據庫資源某指標處于高位的報警,查看數據庫慢日志和會話后均發現有如下的慢SQL:

+--------+---------------+---------------------+--------------------+---------+------+-------+---------------------------------------------------+-----------------+
| ID     | USER          | HOST                | DB                 | COMMAND | TIME | STATE | INFO                                              | SQL_TEMPLATE_ID |
+--------+---------------+---------------------+--------------------+---------+------+-------+---------------------------------------------------+-----------------+
| 951494 | userxxxxxxxxx | 47.100.XX.XX:33830     | analy_db           | Query   |   40 |       | select * from bmsql_oorder where o_carrier_id = 2 | 438b00e4        |
| 952468 | userxxxxxxxxx | 47.100.XX.XX:33517     | analy_db           | Query   |   43 |       | select * from bmsql_order_line where ol_o_id = 2  | 57a572f9        |
| 953468 | userxxxxxxxxx | 47.100.XX.XX:33527     | analy_db           | Query   |   43 |       | select * from bmsql_new_order where no_w_id = 2   | de6eefdb        |
+--------+---------------+---------------------+--------------------+---------+------+-------+---------------------------------------------------+-----------------+
            

此種情況較為復雜,如果一條明顯執行效率很高的SQL也成了慢SQL,則不排除是由于網絡抖動或者服務節點異常等原因導致運行效率降低從而產生大批量的慢SQL,也可能是由于真正的爛SQL完全耗盡了資源,導致原本正常的SQL也成了慢SQL,需要通過SQL分析具體原因,不在本文的討論范圍內。假設已經確定了需要限流的慢SQL,則可以針對每個模板ID創建一個限流規則。但隨著限流規則增加,匹配效率會略有降低,當PolarDB-X的CN內核版本為5.4.11以上時,推薦您使用多模板限流:

CREATE CCL_RULE `KILL_CCL`       //限流規則名稱為KILL_CCL
    ON `analy_db`.`*`              //&匹配analy_db下的所有表上執行的SQL
  TO 'userxxxxxxxxx'@'%'         //&匹配來自userxxxxxxxxx用戶的SQL
    FOR SELECT                     //&匹配是SELECT類型的SQL語句
  FILTER BY TEMPLATE('438b00e4','57a572f9','de6eefdb')  //&匹配中其中一個模板ID,則該匹配項算匹配成功
  WITH MAX_CONCURRENCY = 0;      //設置單節點并發度為0,不允許匹配到的SQL執行

如果確定會話中的慢SQL是都是需要限流的爛SQL,且PolarDB-X的版本為5.4.11以上時,可以開啟慢SQL限流觸發器。也可以使用實例會話頁面里的SQL限流功能,進行如下操作:

456789

總結

SQL限流為應急措施,可在數據庫由于爛SQL導致效率降低的時候,起到快速恢復的作用。對爛SQL進行限流后,需要將注意力集中在如何優化爛SQL上,并在合適的時機清空SQL限流規則。