wink

有的鸟是不会被关住的, 因为它们的羽毛太耀眼了.

  • main

wink

有的鸟是不会被关住的, 因为它们的羽毛太耀眼了.

  • main

tidb笔记2

2016-04-06

tidb index实现

tidb的index不像mysql那样, 由于tidb对于value的获取全是kv操作, 不需要有实质的查询优化, 其只需要用来保证相应的unique等特性跟mysql表现一致即可, 所以tidb的index更多的像是一种兼容的方案.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func (t *Table) AddRecord(ctx context.Context, r []interface{}) (recordID int64, err error) {
...
for _, v := range t.indices { // 遍历table每一个index的组合
if v == nil {
continue
}
colVals, _ := v.FetchValues(r)
if err = v.X.Create(txn, colVals, recordID); err != nil {
if errors2.ErrorEqual(err, kv.ErrKeyExists) {
// Get the duplicate row handle
// For insert on duplicate syntax, we should update the row
iter, _, terr := v.X.Seek(txn, colVals)
if terr != nil {
return 0, errors.Trace(terr)
}
_, h, terr := iter.Next()
if terr != nil {
return 0, errors.Trace(terr)
}
return h, errors.Trace(err)
}
return 0, errors.Trace(err)
}
}
...
}

这是Insert语句中涉及到Index的部分, 首先, 它会从插入的值中获得对应的index列, 在v.FetchValues中获得:

1
2
3
4
5
6
7
8
9
10
func (idx *IndexedCol) FetchValues(r []interface{}) ([]interface{}, error) {
var vals []interface{}
for _, ic := range idx.Columns {
if ic.Offset < 0 || ic.Offset > len(r) {
return nil, errors.New("Index column offset out of bound")
}
vals = append(vals, r[ic.Offset])
}
return vals, nil
}

然后调用v.X.Create尝试创建这个index:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func (c *kvIndex) Create(txn Transaction, indexedValues []interface{}, h int64) error {
keyBuf, err := c.genIndexKey(indexedValues, h)
if err != nil {
return errors.Trace(err)
}
if !c.unique {
// TODO: reconsider value
err = txn.Set(keyBuf, []byte("timestamp?"))
return errors.Trace(err)
}
// unique index
_, err = txn.Get(keyBuf)
if IsErrNotFound(err) {
err = txn.Set(keyBuf, encodeHandle(h)) // 将本条记录的id, recordid当成value写入
return errors.Trace(err)
}
return errors.Trace(ErrKeyExists)
}

我们先来看看unique index的key value, 它会把当前的recordid(这条记录唯一的id)当成value存起来, 这个在处理duplicate update的时候有用:

1
2
3
4
5
6
7
8
9
if err = v.X.Create(txn, colVals, recordID); err != nil {
if errors2.ErrorEqual(err, kv.ErrKeyExists) {
iter, _, terr := v.X.Seek(txn, colVals)
if terr != nil {
return 0, errors.Trace(terr)
}
_, h, terr := iter.Next()
...
}

当index重复了, 而且其为unique的, 不允许重复的话, 那么Create会返回kv.ErrKeyExists error, 那么从这个Index的value中找到其相应的recordid, 用于在Duplicate update的时候获取该条记录, 来进行Update某些列

如果非Unique index, 其值当前还未用上, 就随便用了一个"timestamp?", 来存.

kvIndex.genIndexKey

我们再来看看c.genIndexKey到底是怎么来产生index相应的key的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func (c *kvIndex) genIndexKey(indexedValues []interface{}, h int64) ([]byte, error) {
var (
encVal []byte
err error
)
// only support single value index
if !c.unique {
encVal, err = EncodeValue(append(indexedValues, h)...)
} else {
/*
See: https://dev.mysql.com/doc/refman/5.7/en/create-index.html
A UNIQUE index creates a constraint such that all values in the index must be distinct.
An error occurs if you try to add a new row with a key value that matches an existing row.
For all engines, a UNIQUE index permits multiple NULL values for columns that can contain NULL.
*/
containsNull := false
for _, cv := range indexedValues {
if cv == nil {
containsNull = true
}
}
if containsNull {
encVal, err = EncodeValue(append(indexedValues, h)...)
} else {
encVal, err = EncodeValue(indexedValues...)
}
}
if err != nil {
return nil, errors.Trace(err)
}
buf := append([]byte(nil), []byte(c.prefix)...)
buf = append(buf, encVal...)
return buf, nil
}

参数h是recordId, indexedValues是插入值中对应Index列的几个值. 可以看到index key的结构如下:
| indexPrefix | encValue |

encValue:

可以看到, 如果是非Unique的, 那么这个index不会对插入的值造成任何约束, 也就是这条Insert一定会成功, 那么, 怎么样就不会在leveldb中找到相应的值呢, 只要把recordid绑定起来, 那么这个index key就是全局唯一的了:

1
2
3
if !c.unique {
encVal, err = EncodeValue(append(indexedValues, h)...)
}

如果是Unique的, 那么, 如果相同的值, 是需要报告Duplicate错误的, 那么, 就不能绑定recordid了:

1
2
3
4
5
6
7
8
9
10
11
containsNull := false
for _, cv := range indexedValues {
if cv == nil {
containsNull = true
}
}
if containsNull {
encVal, err = EncodeValue(append(indexedValues, h)...) // 可是为什么这里还是绑定了recordID呢?
} else {
encVal, err = EncodeValue(indexedValues...)
}

为什么在插入index列中出现了nil, 就可以不用管Unique约束呢, 我们看到其注释如下:

/
See: https://dev.mysql.com/doc/refman/5.7/en/create-index.html
A UNIQUE index creates a constraint such that all values in the index must be distinct.
An error occurs if you try to add a new row with a key value that matches an existing row.
For all engines, a UNIQUE index permits multiple NULL values for columns that can contain NULL.
/

答案就是: a UNIQUE index permits multiple NULL values for columns that can contain NULL. 在之前tidb在:

1
2
3
if err = column.CheckNotNull(tableCols, r); err != nil {
return nil, errors.Trace(err)
}

中判断了这些列是否允许NULL了, 所以这里出现NULL是肯定合法的.

index列为nil的情形

问题: mysql是否所有index都为NOT NULL? 否, 只有primary有这个要求

是否所有的index都会有default value, 或者说所有的列都会有default value?

func (t *Table) AddRecord(ctx context.Context, r []interface{}) (recordID int64, err error)

也就是参数r数组里面的全部元素都不为nil?

答案: 除非是自己用insert into test(id, xxx) values(1, NULL), 该元素为nil以外, 其他未指定的(除了auto-increment列), 都会通过InsertIntoStmt.initDefaultValues获得默认值.

这不是一个BUG

下面的语句错误? 这是因为 test1是 int类型

1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> desc nosharding_test;
+-------+------------------+------+------+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+------+---------+----------------+
| id | int(11) UNSIGNED | NO | PRI | NULL | auto_increment |
| test1 | int(5) | YES | | NULL | |
+-------+------------------+------+------+---------+----------------+
mysql> insert into nosharding_test(id, test1) values(3, "NULL");
ERROR 1105 (HY000): strconv.ParseFloat: parsing "NULL": invalid syntax
mysql> insert into nosharding_test(id, test1) values(2, NULL);
Query OK, 1 row affected (0.01 sec)

在MYSQL中执行会这样:

1
2
mysql> insert into nosharding_test(id, test1) values(3, "NULL");
ERROR 1366 (HY000): Incorrect integer value: 'NULL' for column 'test1' at row 1

insert Table数据存放方式

在Table.AddRecord是增加一行记录的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func (t *Table) AddRecord(ctx context.Context, r []interface{}) (recordID int64, err error) {
...
k := t.RecordKey(recordID, nil)
// A new row with current txn-id as lockKey
err = txn.Set([]byte(k), []byte(txn.String())) // recordID的Value是当前的txn-id
if err != nil {
return 0, err
}
// column key -> column value
for _, c := range t.Cols() {
k := t.RecordKey(recordID, c) // 每一个列都是一个Kev-value, Key跟Column.ID相关
if err := t.SetColValue(txn, k, r[c.Offset]); err != nil {
return 0, err
}
}
variable.GetSessionVars(ctx).AddAffectedRows(1)
return recordID, nil
}
...
func (t *Table) RecordKey(h int64, col *column.Col) []byte {
if col != nil { // Key跟Column.ID相关
return util.EncodeRecordKey(t.KeyPrefix(), h, col.ID)
}
return util.EncodeRecordKey(t.KeyPrefix(), h, 0)
}

mysql中的Integer Types

mysql doc

MYSQL支持以上的整数类型, 所以Create table的时候跟在INT后面的长度是没用的, 如INT(5), 都是只有4个字节长

赏

thanks

  • tidb
  • tech

扫一扫,分享到微信

微信分享二维码
golang vendor
tidb笔记1
© 2019 wink