本文介紹了分析和處理數據傾斜問題的方法。

概述

PolarDB-X是由阿里巴巴自主研發的PolarDB分布式版數據庫,在物理資源上是由多個節點所組成的分布式集群。通過數據分區的方式,可以將數據分布到集群中的多個存儲節點,發揮多個節點的存儲和計算能力。

當數據分布不均勻,大部分數據集中在一兩個節點時,將導致節點負載過高、查詢緩慢,甚至造成節點故障,這種現象稱之為數據傾斜。這類問題無法通過擴容來解決,需要使用本文介紹的方法分析和處理。

問題分析

數據傾斜問題,DRDS模式數據庫可按照分庫級、分表級的方式進行分析。AUTO模式數據庫可按照分區級別進行排查。

分庫級別的數據傾斜
執行show db status 語句,能夠顯示當前數據庫中的所有物理庫的數據大小,部分參數說明如下:
  • PHYSICAL_DB:物理庫名
  • SIZE_IN_MB:數據大小
  • RATIO:數據比例
show db status;

返回信息如下:

+----+---------------------------+--------------------+---------------------------+------------+--------+----------------+
| ID | NAME                      | CONNECTION_STRING  | PHYSICAL_DB               | SIZE_IN_MB | RATIO  | THREAD_RUNNING |
+----+---------------------------+--------------------+---------------------------+------------+--------+----------------+
| 1  | hehe@polardbx-polardbx    | 47.100.XX.XX:3306 | TOTAL                     |  0.875     | 100%   | 1              |
| 2  | hehe@polardbx-polardbx    | 47.100.XX.XX:3306 | hehe_000000               |  0.203125  | 23.21% |                |
| 3  | hehe@polardbx-polardbx    | 47.100.XX.XX:3306 | hehe_000001               |  0.203125  | 23.21% |                |
| 4  | hehe@polardbx-polardbx    | 47.100.XX.XX:3306 | hehe_000002               |  0.203125  | 23.21% |                |
| 5  | hehe@polardbx-polardbx    | 47.100.XX.XX:3306 | hehe_000003               |  0.203125  | 23.21% |                |
| 6  | hehe@polardbx-polardbx    | 47.100.XX.XX:3306 | hehe_single               |  0.0625    | 7.14%  |                |
+----+---------------------------+--------------------+---------------------------+------------+--------+----------------+

6 rows in set

在數據傾斜的情況下,多個物理庫的數據大小SIZE_IN_MB和數據所占比率RATIO會相差較大。對于其中數據量較多的分庫,可以通過分表級的信息進一步確認該分庫中的數據屬于哪些數據表。

分表級別數據傾斜
  • 執行show table status語句,查看當前庫的所有數據表大小。部分參數說明如下:
    • ROWS:近似的數據行數
    • DATA_LENGTH:近似的數據量
    show table status;

    返回信息如下:

    +----------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
    | NAME     | ENGINE | VERSION | ROW_FORMAT | ROWS | AVG_ROW_LENGTH | DATA_LENGTH | MAX_DATA_LENGTH | INDEX_LENGTH | DATA_FREE | AUTO_INCREMENT | CREATE_TIME         | UPDATE_TIME | CHECK_TIME | COLLATION          | CHECKSUM | CREATE_OPTIONS | COMMENT |
    +----------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
    | test_tb  | InnoDB | 10      | Dynamic    | 0    | 0              | 131072      | 0               | 131072       | 0         | 100000         | 2021-08-19 07:40:07 | <null>      | <null>     | utf8mb4_general_ci | <null>   |                |         |
    | test_tb1 | InnoDB | 10      | Dynamic    | 0    | 0              | 65536       | 0               | 65536        | 0         | 100000         | 2021-08-19 07:52:24 | <null>      | <null>     | utf8mb4_general_ci | <null>   |                |         |
    +----------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
    
    2 rows in set
  • 執行show table info from $TABLE語句,分析test_tb表中各個分表的數據大小,示例如下:
    show create table test_tb\G

    返回信息如下:

    ***************************[ 1. row ]***************************
    Table        | test_tb
    Create Table | CREATE TABLE `test_tb` (
            `id` int(11) DEFAULT NULL,
            `c1` bigint(20) DEFAULT NULL,
            `c2` varchar(100) DEFAULT NULL,
            KEY `auto_shard_key_id` USING BTREE (`id`)
    ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4  dbpartition by hash(`id`) tbpartition by hash(`id`) tbpartitions 2
    show table info from test_tb;
    說明PolarDB-X的5.4.11及以上版本支持該命令。

    返回信息如下:

    +----+--------------------+----------------+------------+
    | ID | GROUP_NAME         | TABLE_NAME     | SIZE_IN_MB |
    +----+--------------------+----------------+------------+
    | 0  | test_polarx_000000 | test_tb_hg6z_0 | 0.03125    |
    | 1  | test_polarx_000000 | test_tb_hg6z_1 | 0.03125    |
    | 2  | test_polarx_000001 | test_tb_hg6z_2 | 0.03125    |
    | 3  | test_polarx_000001 | test_tb_hg6z_3 | 0.03125    |
    | 4  | test_polarx_000002 | test_tb_hg6z_4 | 0.03125    |
    | 5  | test_polarx_000002 | test_tb_hg6z_5 | 0.03125    |
    | 6  | test_polarx_000003 | test_tb_hg6z_6 | 0.03125    |
    | 7  | test_polarx_000003 | test_tb_hg6z_7 | 0.03125    |
    +----+--------------------+----------------+------------+
    8 rows in set

test_tb表的拆分語句是dbpartition by hash(id)tbpartition by hash(id) tbpartitions 2,因此有4個分庫,8個分表。以上的show table info from test_tb命令中, SIZE_IN_MB即每個分表的數據大小。

如果分表之間的數據容量相差較多,表示發生了分表的數據傾斜,可能是由于tbpartition by的拆分不當導致的,如果分庫之間的數據量相差較多,表示發生了分庫的數據傾斜,可能是由于dbpartition by拆分不當導致的。

分區級數據傾斜

對于PolarDB-X的分區表來說,支持更靈活的數據拆分方式,即LIST、HASH或RANGE分區,以及靈活的分區分裂、合并、遷移。

對于分區表來說,同樣支持通過show table info from $TABLE命令查詢每個分表的物理大小。

除此之外,分區表還支持通過select * from information_schema.table_detail where logical_table='test_tb' 查詢分區級的詳細信息,部分參數說明如下:
  • PARTITION_NAME:分區名
  • TABLE_ROWS:分區的數據行數
  • DATA_LENGTH:分區的數據大小
  • PERCENT:分區的數據比例

返回信息如下:

+-------------+------------------+---------------+----------------+---------------+----------------+------------+-------------+--------------+----------------------------------------------+------------------------------------+
| SCHEMA_NAME | TABLE_GROUP_NAME | LOGICAL_TABLE | PHYSICAL_TABLE | PARTITION_SEQ | PARTITION_NAME | TABLE_ROWS | DATA_LENGTH | INDEX_LENGTH | BOUND_VALUE                                  | PERCENT                            |
+-------------+------------------+---------------+----------------+---------------+----------------+------------+-------------+--------------+----------------------------------------------+------------------------------------+
| partdb_test | tg73             | test_tb       | test_tb_00000  | 0             | p1             | 0          | 16384       | 16384        | [MINVALUE, -6917529027641081843)             | 0.00%├-------------------------┤   |
| partdb_test | tg73             | test_tb       | test_tb_00001  | 1             | p2             | 1          | 16384       | 16384        | [-6917529027641081843, -4611686018427387893) | 9.09%├███-----------------------┤  |
| partdb_test | tg73             | test_tb       | test_tb_00002  | 2             | p3             | 1          | 16384       | 16384        | [-4611686018427387893, -2305843009213693943) | 9.09%├███-----------------------┤  |
| partdb_test | tg73             | test_tb       | test_tb_00003  | 3             | p4             | 0          | 16384       | 16384        | [-2305843009213693943, 7)                    | 0.00%├-------------------------┤   |
| partdb_test | tg73             | test_tb       | test_tb_00004  | 4             | p5             | 6          | 16384       | 16384        | [7, 2305843009213693957)                     | 54.55%├██████████████------------┤ |
| partdb_test | tg73             | test_tb       | test_tb_00005  | 5             | p6             | 2          | 16384       | 16384        | [2305843009213693957, 4611686018427387907)   | 18.18%├█████---------------------┤ |
| partdb_test | tg73             | test_tb       | test_tb_00006  | 6             | p7             | 1          | 16384       | 16384        | [4611686018427387907, 6917529027641081857)   | 9.09%├███-----------------------┤  |
| partdb_test | tg73             | test_tb       | test_tb_00007  | 7             | p8             | 0          | 16384       | 16384        | [6917529027641081857, 9223372036854775807)   | 0.00%├-------------------------┤   |
+-------------+------------------+---------------+----------------+---------------+----------------+------------+-------------+--------------+----------------------------------------------+------------------------------------|

8 rows in set

在以上示例中,分區p5的數據量明顯多于其他分區,存在數據傾斜。

解決方法

數據傾斜通常是由于數據拆分的方式不當造成的,常見原因如下:
  • 使用了不恰當的拆分函數,例如UNI_HASH ,但拆分鍵不具備均勻分布的特征;
  • 拆分鍵的區分度過低,例如HASH分區,按照省份拆分,但省份實際較少,容易造成數據不均;
  • 某些拆分鍵存在較多的數據,例如訂單表按照賣家ID進行拆分,部分的大賣家可能存在較多的數據。
分庫分表方式調整
對于拆分方式選擇不當導致的數據傾斜問題,通常需要調整拆分方式,包括以下兩方面:
  • 調整拆分函數:分庫分表可以選擇HASH、UNI_HASH或STR_HASH等拆分函數;分區表可采用HASH、KEYS、RANGE或RANGE COLUMN等拆分方式;
  • 調整拆分鍵:
    • 選擇較為均勻,不存在熱點的拆分鍵;
    • 選擇區分度較高的拆分鍵,避免HASH結果不均勻;
    • 大部分查詢都通過拆分鍵做等值查詢,盡量避免查詢多個分片。
在選擇好數據拆分方式之后,可以通過如下方法對數據表進行調整:
  • 重建表:重建另一個新的表,將舊表的數據導入。
    說明 此方法需要先停止業務寫入。
  • 在線調整分區:通過變更表類型及拆分規則在線修改分區方式;無需停止業務寫入,但此過程仍然需要重寫全表數據,開銷較大,需要在業務低峰期執行。
示例:發現test_tb表存在數據傾斜,原因在于數據拆分鍵使用不當,因此可以通過以下語句將拆分鍵調整成hash(order_id)
ALTER TABLE test_tb dbpartition BY hash(`order_id`);
分區調整

PolarDB-X中,實現了更靈活的基于分區表的數據分布,因此可以實現分區級的分裂及遷移,解決數據傾斜問題。分區調整能夠解決的場景主要是分區過大導致的數據傾斜,不適用于拆分函數選擇不當等問題。

以Range分區舉例:
  1. 建表時指定兩個分區,p0和p1,其范圍分別是 [-inf,1000), [1000,2000);
  2. 發現分區p0數據過多,存在數據傾斜,因此將分區p0進行分裂,使其分布到多個節點;
  3. 默認新建的分區會創建到數據量最少的節點上,如果不滿足需求,可另外進行分區遷移。
CREATE TABLE `table_range` (
        `id` int(11) DEFAULT NULL
) PARTITION BY RANGE(`id`)
(PARTITION p0 VALUES LESS THAN (1000),
 PARTITION p1 VALUES LESS THAN (2000)
) /* tablegroup = `tg110` */ ;

ALTER TABLEGROUP tg110 SPLIT PARTITION p0 INTO 
(partition p0_1 values less than (500), 
 partition p0_2 values less than (1000) );
重要 SPLIT PARTITION操作的對象是TABLEGROUP而非單個表,通過SHOW FULL CREATE TABLE命令可以查看一個表所屬的tablegroup。