1. 繼續一下昨天的話題
昨天我學習了查詢優化器的部分內容,其實主要是起了一個提綱挈領的作用,真的內容我還沒有學習。那麼今天首先歪個樓,寫一點Go語言相關的東西,畢竟我的目的是在學習RadonDB源碼的同時學習Go語言。
plans := planner.NewPlanTree()
switch node.(type) {
case *sqlparser.DDL:
node := planner.NewDDLPlan(log, database, query, node.(*sqlparser.DDL), router)
plans.Add(node)
case *sqlparser.Insert:
node := planner.NewInsertPlan(log, database, query, node.(*sqlparser.Insert), router)
plans.Add(node)
case *sqlparser.Delete:
node := planner.NewDeletePlan(log, database, query, node.(*sqlparser.Delete), router)
plans.Add(node)
case *sqlparser.Update:
node := planner.NewUpdatePlan(log, database, query, node.(*sqlparser.Update), router)
plans.Add(node)
case *sqlparser.Select:
nod := node.(*sqlparser.Select)
selectNode := planner.NewSelectPlan(log, database, query, nod, router)
plans.Add(selectNode)
case *sqlparser.Checksum:
node := planner.NewOthersPlan(log, database, query, node, router)
plans.Add(node)
default:
return nil, errors.Errorf("optimizer.unsupported.query.type[%+v]", node)
首先呢,代碼新建了一個PlanTree指針類型的plans。然後呢,根據不同的node類型判斷分支,每一個分支裏都會新建一個不同類型的Plan,然後把這個Plan放入到plans裏。
看到這裏我有點恍惚了,這有點像多態啊。我們來看看NewPlanTree都幹了什麼:
type Plan interface {
Build() error
Type() PlanType
JSON() string
Size() int
Children() *PlanTree
}
// PlanTree is a container for all plans
type PlanTree struct {
size int
children []Plan
}
// NewPlanTree creates the new plan tree.
func NewPlanTree() *PlanTree {
return &PlanTree{
children: make([]Plan, 0, 8),
}
}
其實就是新建了一個Plan類型的切片放在了PlanTree裏。Plan是一個接口,有了接口就能實現多態,這是我之前講過的:
剛剛好,不管是ddl_plan還是其他的plan,都實現了這個接口,也就是說實現了多態,可以將這些plan全都放在plan類型的切片裏了。
Go的多態實現,或者說接口,我個人覺得沒有Java好理解。
2. DDL Plan在幹什麼
其實DDL本無所謂什麼plan,但是自從有了分庫,就有了plan。
----周樹人
分庫的情況下,一切都變得比較複雜和迷亂了。其實撥開迷霧,總能看到一絲光亮,ddl_plan其實做了這麼幾件事:
判斷幾種非法操作;
進行簡單的語法解析和替換。
先說幾種非法操作,這個對於DBA來說還是應該知道並且爛熟於心的:
不能刪除shard key所在的列;
shard key的列不能被修改;
只能在shard key所在的列上做唯一約束和主鍵。
接下來要遍歷所有的segment,那麼又引進了一個概念叫做segment。segment看起來是個元組:
// Segment tuple.
type Segment struct {
// Segment table name.
Table string `json:",omitempty"`
// Segment backend name.
Backend string `json:",omitempty"`
// key range of this segment.
Range KeyRange `json:",omitempty"`
}
其實接下來的代碼就是對這個元組中的元素進行簡單的處理了:
segTable := segment.Table
if node.Table.Qualifier.IsEmpty() {
segTable = fmt.Sprintf("`%s`.`%s`", database, segTable)
rawQuery := strings.Replace(p.RawQuery, "`", "", 2)
re, _ := regexp.Compile(fmt.Sprintf(`\b(%s)\b`, table))
query = re.ReplaceAllString(rawQuery, segTable)
} else {
segTable = fmt.Sprintf("`%s`.`%s`", database, segTable)
newTable := fmt.Sprintf("%s.%s", database, table)
rawQuery := strings.Replace(p.RawQuery, "`", "", 4)
re, _ := regexp.Compile(fmt.Sprintf(`\b(%s)\b`, newTable))
query = re.ReplaceAllString(rawQuery, segTable)
}
代碼邏輯很簡單,會把我們在命令行上輸入的類似create之類的語句進行一些字符串上的處理,主要是格式化輸出和一些字符串替換等等,就不攤開講了,沒啥意思。
tuple := xcontext.QueryTuple{
Query: query,
Backend: segment.Backend,
Range: segment.Range.String(),
}
p.Querys = append(p.Querys, tuple)
接下來就是把剛纔格式化好的查詢,拼接在query字符串中。這一步完成之後,就完成了DDLPlan的構建。
其實,DDL的plan也沒什麼精彩的地方。
這世界上本來沒有ddl的執行計劃,有了分庫之後,便有了ddl的執行計劃,其實有和沒有,終究是一樣的。
----周樹人
3. 小結
我猜,Insert的DDL會更有意思。
寫入數據,本來是簡單的事情,但是有了分庫,這一切終究不再簡單了。
----周樹人