学习RadonDB源码(五)
1. 分布式数据库引入了不少新问题
移动互联没有兴起,3G没有开始之前,我们玩单机数据库玩的不亦乐乎,而且也玩的很好了。
但是时代的大潮就是这么浩浩汤汤,3G时代开始,移动互联网兴起,4G,5G的到来,给我们带来了方便的同时,也带来了海量的数据。此时我们的单机数据库就开始逐渐无法处理这么多数据库了,单纯的提升硬件水平已经无法解决这样的数据了。
此时就只剩分布式一条路可以走了,服务分布式,数据库也要分布式。可是站在分布式这个十字路口上,传统的关系型数据库似乎有点无所适从。这种无所适从给了noSQL一个机会,我们见证了类似MongoDB等等非关系型数据库大放异彩。
不过noSQL也没有能够解决所有问题,人们还是需要关系型数据模型的,于是很多聪明的大神们就搞出了很多新的东西来,有改造MySQL的像我一直在研究的RadonDB,有兼容MySQL协议的NewSQL,比如TiDB。
分布式,总是让事情变得复杂一些。
比如我们都知道的CAP理论,比如Raft协议。不过还好我们有了很多现成的方案可以使用。
2. 继续昨天的话题
昨天讲到了DDL的优化器,比较无聊,因为无非是将DDL语句做了一些格式化的操作。
今天看看insert的优化器。
所有的逻辑还是在Build方法中,首先需要注意的一个逻辑是这一段代码:
rows, ok := node.Rows.(sqlparser.Values)
if !ok {
return errors.Errorf("unsupported: rows.can.not.be.subquery[%T]", node.Rows)
}
这里的代码显示了一条insert语句的规则:
- 不能用insert into table select * from table_a这种语句
RadonDB虽然支持分库,不过也不是所有的表都要sharding,因此这里代码中对没有配置sharding的表进行了逻辑判断:
// Table is global or single table.
if shardKey == "" {
segments, err := p.router.Lookup(database, table, nil, nil)
if err != nil {
return err
}
for _, segment := range segments {
buf := sqlparser.NewTrackedBuffer(nil)
buf.Myprintf("%s %v%sinto %s.%s%v %v%v", node.Action, node.Comments, node.Ignore, database, segment.Table, node.Columns, node.Rows, node.OnDup)
tuple := xcontext.QueryTuple{
Query: buf.String(),
Backend: segment.Backend,
Range: segment.Range.String(),
}
p.Querys = append(p.Querys, tuple)
}
return nil
}
代码到时平平无奇,直接拼出insert语句就可以了,这种没有sharding的表其实和单机时代的表没有什么区别了。
下面又要写一条规则:
- 不支持insert into ... on duplicate update语法
代码逻辑是这样的:
// Check the OnDup.
if len(node.OnDup) > 0 {
// analyze shardkey changing.
if isShardKeyChanging(sqlparser.UpdateExprs(node.OnDup), shardKey) {
return errors.New("unsupported: cannot.update.shard.key")
}
}
如果写了这样的语句,就直接报错了。
如果是sharding表,那么处理逻辑是这样的:
// Find the shard key index.
idx := -1
for i, column := range node.Columns {
if column.String() == shardKey {
idx = i
break
}
}
if idx == -1 {
return errors.Errorf("unsupported: shardkey.column[%v].missing", shardKey)
}
首先遍历所有的column,直到找到shardKey所在的那一列。这里能看出来,RadonDB支持的是单个key进行sharding,因为匹配成功后循环就跳出了。
for _, row := range rows {
if idx >= len(row) {
return errors.Errorf("unsupported: shardkey[%v].out.of.index:[%v]", shardKey, idx)
}
shardVal, ok := row[idx].(*sqlparser.SQLVal)
if !ok {
return errors.Errorf("unsupported: shardkey[%v].type.canot.be[%T]", shardKey, row[idx])
}
segments, err := p.router.Lookup(database, table, shardVal, shardVal)
if err != nil {
return err
}
rewrittenTable := segments[0].Table
backend := segments[0].Backend
rangi := segments[0].Range.String()
val, ok := vals[rewrittenTable]
if !ok {
val = &valTuple{
backend: backend,
table: rewrittenTable,
rangi: rangi,
vals: make(sqlparser.Values, 0, 16),
}
vals[rewrittenTable] = val
}
val.vals = append(val.vals, row)
}
这里又能引出一条规则:
- shard key的类型限制在以下几种类型:
居然还有Hex类型,不过我们平时用int或者String比较多吧,我想很多人都会这选择。
接下来的逻辑和之前写的DDL的差不多,就是遍历segment,要注意,此时是在循环中的,就是对要写入的行一行一行的循环遍历中的。
rewrittenTable := segments[0].Table
backend := segments[0].Backend
rangi := segments[0].Range.String()
val, ok := vals[rewrittenTable]
if !ok {
val = &valTuple{
backend: backend,
table: rewrittenTable,
rangi: rangi,
vals: make(sqlparser.Values, 0, 16),
}
vals[rewrittenTable] = val
}
val.vals = append(val.vals, row)
我们可以认为这段代码将要写入的值,对应的分片条件都整合在了一起,放在了一个map中。
代码逻辑的最后,会将查询和路由信息一起写入plan的Query域中。
这就比DDL的优化器要好玩一点了,因为还有设计到分片路由信息的部分。我估计查询计划会更有意思。
3. 小结
我们来打算调试的,但是Windows下竟然会出现编译失败的问题,我查了一下错误的原因,似乎就是因为平台问题。而且我在MacOS上编译运行也是没有问题的。
还是期待官方早点写详细的中文文档。