摘要:部分主要流程如下把上文提到語法解析階段會把語句中相關信息轉換成然后負責把結構轉換即的元信息。最后把的元信息追加到的元信息中,具體實現在這里。會把要刪除的分區從元信息刪除掉,刪除前會做的檢查。
作者:肖亮亮
Table Partition 什么是 Table PartitionTable Partition 是指根據一定規則,將數據庫中的一張表分解成多個更小的容易管理的部分。從邏輯上看只有一張表,但是底層卻是由多個物理分區組成。相信對有關系型數據庫使用背景的用戶來說可能并不陌生。
TiDB 正在支持分區表這一特性。在 TiDB 中分區表是一個獨立的邏輯表,但是底層由多個物理子表組成。物理子表其實就是普通的表,數據按照一定的規則劃分到不同的物理子表類內。程序讀寫的時候操作的還是邏輯表名字,TiDB 服務器自動去操作分區的數據。
分區表有什么好處?優化器可以使用分區信息做分區裁剪。在語句中包含分區條件時,可以只掃描一個或多個分區表來提高查詢效率。
方便地進行數據生命周期管理。通過創建、刪除分區、將過期的數據進行 高效的歸檔,比使用 Delete 語句刪除數據更加優雅,打散寫入熱點,將一個表的寫入分散到多個物理表,使得負載分散開,對于存在 Sequence 類型數據的表來說(比如 Auto Increament ID 或者是 create time 這類的索引)可以顯著地提升寫入吞吐。
分區表的限制TiDB 默認一個表最多只能有 1024 個分區 ,默認是不區分表名大小寫的。
Range, List, Hash 分區要求分區鍵必須是 INT 類型,或者通過表達式返回 INT 類型。但 Key 分區的時候,可以使用其他類型的列(BLOB,TEXT 類型除外)作為分區鍵。
如果分區字段中有主鍵或者唯一索引的列,那么有主鍵列和唯一索引的列都必須包含進來。即:分區字段要么不包含主鍵或者索引列,要么包含全部主鍵和索引列。
TiDB 的分區適用于一個表的所有數據和索引。不能只對表數據分區而不對索引分區,也不能只對索引分區而不對表數據分區,也不能只對表的一部分數據分區。
常見分區表的類型
Range 分區:按照分區表達式的范圍來劃分分區。通常用于對分區鍵需要按照范圍的查詢,分區表達式可以為列名或者表達式 ,下面的 employees 表當中 p0, p1, p2, p3 表示 Range 的訪問分別是 ?(min, 1991), [1991, 1996), [1996, 2001), [2001, max) 這樣一個范圍。
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), separated DATE NOT NULL ) PARTITION BY RANGE ( YEAR(separated) ) ( PARTITION p0 VALUES LESS THAN (1991), PARTITION p1 VALUES LESS THAN (1996), PARTITION p2 VALUES LESS THAN (2001), PARTITION p3 VALUES LESS THAN MAXVALUE );
List 分區:按照 List 中的值分區,主要用于枚舉類型,與 Range 分區的區別在于 Range 分區的區間范圍值是連續的。
Hash 分區:Hash 分區需要指定分區鍵和分區個數。通過 Hash 的分區表達式計算得到一個 INT 類型的結果,這個結果再跟分區個數取模得到具體這行數據屬于那個分區。通常用于給定分區鍵的點查詢,Hash 分區主要用來分散熱點讀,確保數據在預先確定個數的分區中盡可能平均分布。
Key 分區:類似 Hash 分區,Hash 分區允許使用用戶自定義的表達式,但 Key 分區不允許使用用戶自定義的表達式。Hash 僅支持整數分區,而 Key 分區支持除了 Blob 和 Text 的其他類型的列作為分區鍵。
TiDB Table Partition 的實現本文接下來按照?TiDB 源碼的 release-2.1 分支講解,部分講解會在 source-code 分支代碼,目前只支持 Range 分區所以這里只介紹 Range 類型分區 Table Partition 的源碼實現,包括 create table、select 、add partition、insert 、drop partition 這五種語句。
create tablecreate table 會重點講構建 Partition 的這部分,更詳細的可以看 TiDB 源碼閱讀系列文章(十七)DDL 源碼解析,當用戶執行創建分區表的SQL語句,語法解析(Parser)階段會把 SQL 語句中 Partition 相關信息轉換成 ast.PartitionOptions,下文會介紹。接下來會做一系列 Check,分區名在當前的分區表中是否唯一、是否分區 Range 的值保持遞增、如果分區鍵構成為表達式檢查表達式里面是否是允許的函數、檢查分區鍵必須是 INT 類型,或者通過表達式返回 INT 類型、檢查分區鍵是否符合一些約束。
解釋下分區鍵,在分區表中用于計算這一行數據屬于哪一個分區的列的集合叫做分區鍵。分區鍵構成可能是一個字段或多個字段也可以是表達式。
// PartitionOptions specifies the partition options. type PartitionOptions struct { Tp model.PartitionType Expr ExprNode ColumnNames []*ColumnName Definitions []*PartitionDefinition } ? // PartitionDefinition defines a single partition. type PartitionDefinition struct { Name model.CIStr LessThan []ExprNode MaxValue bool Comment string }
PartitionOptions 結構中 Tp 字段表示分區類型,Expr 字段表示分區鍵,ColumnNames 字段表示 Columns 分區,這種類型分區有分為 Range columns 分區和 List columns 分區,這種分區目前先不展開介紹。PartitionDefinition 其中 Name 字段表示分區名,LessThan 表示分區 Range 值,MaxValue 字段表示 Range 值是否為最大值,Comment 字段表示分區的描述。
CreateTable Partition 部分主要流程如下:
把上文提到語法解析階段會把 SQL語句中 Partition 相關信息轉換成 ast.PartitionOptions , 然后 buildTablePartitionInfo 負責把 PartitionOptions 結構轉換 PartitionInfo, ?即 Partition 的元信息。
checkPartitionNameUnique 檢查分區名是否重復,分表名是不區大小寫的。
對于每一分區 Range 值進行 Check,checkAddPartitionValue 就是檢查新增的 Partition 的 Range 需要比之前所有 Partition 的 Range 都更大。
TiDB 單表最多只能有 1024 個分區 ,超過最大分區的限制不會創建成功。
如果分區鍵構成是一個包含函數的表達式需要檢查表達式里面是否是允許的函數 checkPartitionFuncValid。
檢查分區鍵必須是 INT 類型,或者通過表達式返回 INT 類型,同時檢查分區鍵中的字段在表中是否存在 checkPartitionFuncType。
如果分區字段中有主鍵或者唯一索引的列,那么多有主鍵列和唯一索引列都必須包含進來。即:分區字段要么不包含主鍵或者索引列,要么包含全部主鍵和索引列 checkRangePartitioningKeysConstraints。
通過以上對 PartitionInfo 的一系列 check 主要流程就講完了,需要注意的是我們沒有對 PartitionInfo 的元數據持久化多帶帶存儲而是附加在 TableInfo Partition 中。
add partitionadd partition 首先需要從 SQL 中解析出來 Partition 的元信息,然后對當前添加的分區會有一些 Check 和限制,主要檢查是否是分區表、分區名是已存在、最大分區數限制、是否 Range 值保持遞增,最后把 Partition 的元信息 PartitionInfo 追加到 Table 的元信息 TableInfo中,具體如下:
檢查是否是分區表,若不是分區表則報錯提示。
用戶的 SQL 語句被解析成將 ast.PartitionDefinition 然后 buildPartitionInfo 做的事就是保存表原來已存在的分區信息例如分區類型,分區鍵,分區具體信息,每個新分區分配一個獨立的 PartitionID。
TiDB 默認一個表最多只能有 1024 個分區,超過最大分區的限制會報錯。
對于每新增一個分區需要檢查 Range 值進行 Check,checkAddPartitionValue 簡單說就是檢查新增的 Partition 的 Range 需要比之前所有 Partition 的 Rrange 都更大。
checkPartitionNameUnique 檢查分區名是否重復,分表名是不區大小寫的。
最后把 Partition 的元信息 PartitionInfo 追加到 Table 的元信息 TableInfo.Partition 中,具體實現在這里 updatePartitionInfo。
drop partitiondrop partition 和 drop table 類似,只不過需要先找到對應的 Partition ID,然后刪除對應的數據,以及修改對應 Table 的 Partition 元信息,兩者區別是如果是 drop table 則刪除整個表數據和表的 TableInfo 元信息,如果是 drop partition 則需刪除對應分區數據和 TableInfo 中的 Partition 元信息,刪除分區之前會有一些 Check 具體如下:
只能對分區表做 drop partition 操作,若不是分區表則報錯提示。
checkDropTablePartition 檢查刪除的分區是否存在,TiDB 默認是不能刪除所有分區,如果想刪除最后一個分區,要用 drop table 代替。
removePartitionInfo 會把要刪除的分區從 Partition 元信息刪除掉,刪除前會做checkDropTablePartition 的檢查。
對分區表數據則需要拿到 PartitionID 根據插入數據時候的編碼規則構造出 StartKey 和 EndKey 便能包含對應分區 Range 內所有的數據,然后把這個范圍內的數據刪除,具體代碼實現在這里。
編碼規則:
Key: tablePrefix_rowPrefix_partitionID_rowID
startKey: tablePrefix_rowPrefix_partitionID
endKey: tablePrefix_rowPrefix_partitionID + 1
刪除了分區,同時也將刪除該分區中的所有數據。如果刪除了分區導致分區不能覆蓋所有值,那么插入數據的時候會報錯。
Select 語句Select 語句重點講 Select Partition 如何查詢的和分區裁剪(Partition Pruning),更詳細的可以看 TiDB 源碼閱讀系列文章(六)Select 語句概覽 。
一條 SQL 語句的處理流程,從 Client 接收數據,MySQL 協議解析和轉換,SQL 語法解析,邏輯查詢計劃和物理查詢計劃執行,到最后返回結果。那么對于分區表是如何查詢的表里的數據的,其實最主要的修改是 邏輯查詢計劃 階段,舉個例子:如果用上文中 employees 表作查詢, 在 SQL 語句的處理流程前幾個階段沒什么不同,但是在邏輯查詢計劃階段,rewriteDataSource 將 DataSource 重寫了變成 Union All 。每個 Partition id 對應一個 Table Reader。
select * from employees
等價于:
select * from (union all select * from p0 where id < 1991 select * from p1 where id < 1996 select * from p2 where id < 2001 select * from p3 where id < MAXVALUE)
通過觀察?EXPLAIN 的結果可以證實上面的例子,如圖 1,最終物理執行計劃中有四個 Table Reader 因為 employees 表中有四個分區,Table Reader 表示在 TiDB 端從 TiKV 端讀取,cop task 是指被下推到 TiKV 端分布式執行的計算任務。
用戶在使用分區表時,往往只需要訪問其中部分的分區, 就像程序局部性原理一樣,優化器分析 FROM 和 WHERE 子句來消除不必要的分區,具體還要優化器根據實際的 SQL 語句中所帶的條件,避免訪問無關分區的優化過程我們稱之為分區裁剪(Partition Pruning),具體實現在 這里,分區裁剪是分區表提供的重要優化手段,通過分區的裁剪,避免訪問無關數據,可以加速查詢速度。當然用戶可以刻意利用分區裁剪的特性在 SQL 加入定位分區的條件,優化查詢性能。
Insert 語句Insert 語句 是怎么樣寫入 Table Partition ?
其實解釋這些問題就可以了:
普通表和分區表怎么區分?
插入數據應該插入哪個 Partition?
每個 Partition 的 RowKey 怎么編碼的和普通表的區別是什么?
怎么將數據插入到相應的 Partition 里面?
普通 Table 和 Table Partition 也是實現了 Table 的接口,load schema 在初始化 Table 數據結構的時候,如果發現 tableInfo 里面沒有 Partition 信息,則生成一個普通的 tables.Table,普通的 Table 跟以前處理邏輯保持不變,如果 tableInfo 里面有 Partition 信息,則會生成一個 tables.PartitionedTable,它們的區別是 RowKey 的編碼方式:
每個分區有一個獨立的 Partition ID,Partition ID 和 Table ID 地位平等,每個 Partition 的 Row 和 index 在編碼的時候都使用這個 Partition 的 ID。
下面是 PartitionRecordKey 和普通表 RecordKey 區別。
分區表按照規則編碼成 Key-Value pair:
Key: tablePrefix_rowPrefix_partitionID_rowID
Value: [col1, col2, col3, col4]
普通表按照規則編碼成 Key-Value pair:
Key: tablePrefix_rowPrefix_tableID_rowID
Value: [col1, col2, col3, col4]
通過 locatePartition 操作查詢到應該插入哪個 Partition,目前支持 RANGE 分區插入到那個分區主要是通過范圍來判斷,例如在 employees 表中插入下面的 sql,通過計算范圍該條記錄會插入到 p3 分區中,接著調用對應 Partition 上面的 AddRecord 方法,將數據插入到相應的 Partition 里面。
INSERT INTO employees VALUES (1, "PingCAP TiDB", "2003-10-15"),
插入數據時,如果某行數據不屬于任何 Partition,則該事務失敗,所有操作回滾。如果 Partition 的 Key 算出來是一個 NULL,對于不同的 Partition 類型有不同的處理方式:
對于 Range Partition:該行數據被插入到最小的那個 Partition
對于 List partition:如果某個 Partition 的 Value List 中有 NULL,該行數據被插入那個 Partition,否則插入失敗
對于 Hash 和 Key Partition:NULL 值視為 0,計算 Partition ID 將數據插入到對應的 Partition
在 TiDB 分區表中分區字段插入的值不能大于表中 Range 值最大的上界,否則會報錯
EndTiDB 目前支持 Range 分區類型,具體以及更細節的可以看 這里。剩余其它類型的分區類型正在開發中,后面陸續會和大家見面,敬請期待。它們的源碼實現讀者屆時可以自行閱讀,流程和文中上述描述類似。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/17801.html
摘要:在事務提交結束之后,事務可能提交成功,也可能提交失敗。需要把這個狀態告知如果發生了,那么輸出的類型就為,如果成功提交,那么輸出的類型就為。,當完成自己所有的狀態變更之后,會把的狀態改為。 作者:姚維 TiDB Binlog Overview 這篇文章不是講 TiDB Binlog 組件的源碼,而是講 TiDB 在執行 DML/DDL 語句過程中,如何將 Binlog 數據 發送給 Ti...
摘要:作者王相本文為源碼閱讀系列文章的第七篇,在上篇文章中我們介紹了的實現,主要包括目錄結構定義數據的處理流程主從切換支持的讀取等邏輯。本篇文章我們將會對的定制化數據同步功能進行詳細的講解。 作者:王相 本文為 DM 源碼閱讀系列文章的第七篇,在 上篇文章 中我們介紹了 relay log 的實現,主要包括 relay log 目錄結構定義、relay log 數據的處理流程、主從切換支持、...
閱讀 2547·2021-10-11 10:58
閱讀 1029·2019-08-29 13:58
閱讀 1668·2019-08-26 13:32
閱讀 833·2019-08-26 10:40
閱讀 3261·2019-08-26 10:18
閱讀 1760·2019-08-23 14:18
閱讀 1109·2019-08-23 10:54
閱讀 439·2019-08-22 18:39