对于NewSQL而言,是有表的概念的,即schema的概念。NewSQL以单独的SQL层接入用户的SQL请求,进行Parse、Logic Plan、Physical Plan,之后将对应的计划,下推到KV层进行执行。
只要有表的存在,就会有DDL执行,TiDB的在线schema 变更参考了Google F1 Online, Asynchronous Schema Change in F1
关于这篇论文的讨论,我们放在下一篇进行分析,将结合代码一起讨论。
本篇先只讨论单机情况下,加列并存在默认值时是如何做的,我们以TiDB为例,分析如下场景。
考虑有表t1,有2列,id int primary key , age int . 并且插入2行数据:
id | age |
---|---|
1 | 18 |
2 | 19 |
现在要加入一列,叫 marriaged_age int default 26
alter table t1 ADD marriaged_age int default 26 ;
通常,schema信息作为一个单独的Key—Value对存储在Kv层,当更新这张表的schema信息时,也就是对这个key-value的更新,我们暂时不考虑分布式场景。
id | age | marriaged_age |
---|---|---|
1 | 18 | empty |
2 | 19 | empty |
TiDB会将整行数据按照编码格式编码成一个字符串,作为value。key的构成格式请参考TiDB的key编码
对于存量数据,本身是没有这一列的信息的,那么就有两种做法:
- 给存量数据的每一行填上这一列的值
- 不回填这些值,当根据新的schema信息去解码value时候,发现这一列的值是empty,则用schema信息中的default值作为这一列的value。返回给上层。
TiDB采用第二种方法.
在alter table之后插入的数据,marriaged_age 这一列无论是否insert时候指定,都会有一个对应的值。
insert into t1 values(3,20);
insert into t1 values(4,22,28);
新插入的数据和旧数据如下:
id | age | marriaged_age |
---|---|---|
1 | 18 | empty |
2 | 19 | empty |
3 | 20 | 26 |
4 | 22 | 28 |
若这时,对marriaged_age列的default值再次进行修改时:
alter table t1 CHANGE marriaged_age marriaged_age int default 24 ;
此之后插入的数据该列的值默认值就是24了。
insert into t1 values(5,26);
insert into t1 values(6,27,30);
新插入的数据和旧数据如下:
id | age | marriaged_age |
---|---|---|
1 | 18 | empty |
2 | 19 | empty |
3 | 20 | 26 |
4 | 22 | 28 |
5 | 26 | 24 |
6 | 27 | 30 |
这时候,对于记录id为(1,2)的marriaged_age值,究竟应该解析成26?还是24呢?
当然是26,因为在第一次DDL变更的时候(即添加marriaged_age,就相当于已经对于存量数据做了回填,第二次变更只能对其之后的插入操作造成影响!
TiDB是如何实现这样的效果的呢?
TiDB在存Schema信息的时候,每个列的属性中,有一个 OriginalDefaultValue 和 DefaultValue
// model.go中,对列属性的描述
// ColumnInfo provides meta data describing of a table column.
type ColumnInfo struct {
ID int64 `json:"id"`
Name CIStr `json:"name"`
Offset int `json:"offset"`
OriginDefaultValue interface{} `json:"origin_default"`
DefaultValue interface{} `json:"default"`
DefaultValueBit []byte `json:"default_bit"`
GeneratedExprString string `json:"generated_expr_string"`
GeneratedStored bool `json:"generated_stored"`
Dependences map[string]struct{} `json:"dependences"`
types.FieldType `json:"type"`
State SchemaState `json:"state"`
Comment string `json:"comment"`
// Version means the version of the column info.
// Version = 0: For OriginDefaultValue and DefaultValue of timestamp column will stores the default time in system time zone.
// That is a bug if multiple TiDB servers in different system time zone.
// Version = 1: For OriginDefaultValue and DefaultValue of timestamp column will stores the default time in UTC time zone.
// This will fix bug in version 0. For compatibility with version 0, we add version field in column info struct.
Version uint64 `json:"version"`
}
当value解码后,存在有的列是empty,那么就采用OriginDefaultValue进行填充。
OriginDefaultValue的存在就是为了保证那些在做DDL变更(这里特指ADD Column)之前的行,没有这列数据的value能够有默认值填充。
而DefaultValue就表示的是当下这列实时的default value值,以后对这列的default value 如果再有变更,那么只需要记录在DefaultValue这个变量中就可以了。
感谢磊哥(吕磊)倾情指点
萧然 2019-07-15 09:56
转载请注明出处