暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SQL的生命周期

小张的科学日记 2020-12-09
616

最近在整理关系型数据库的内容,这是第三篇本来计划中的是查询编译,但是有涉及到一些进程模型、词法语法分析、查询执行等的内容,就改成了SQL的一生。

进程模型可以看Anatomy of a Database System这篇论文,我们这里就用简单的协程处理,golang的并发模型是内置n:m的,即它有自己的用户空间的调度器,所以直接用green thread就行。

```go

for {

conn, err := server.listener.Accept()

if err != nil {

if opErr, ok := err.(*net.OpError); ok {

if opErr.Err.Error() == "use of closed network connection" {

return nil

}

}

return err

}

clientConn := server.newConn(conn)

go server.onConn(clientConn)

}

```

适配MySQL协议中,我们需要实现一个MySQL协议的解包拆包器,用过Java的应该了解,就是Netty的Decode/Encode部分,这部分我们从上面的connection中读取字节,将流式的tcp数据转换成我们需要的MySQL协议包。

```go

//读取

func (p *packetIO) readOnePacket() ([]byte, error) {

var header [4]byte

if p.readTimeout > 0 {

if err := p.bufReadConn.SetReadDeadline(time.Now().Add(p.readTimeout)); err != nil {

return nil, err

}

}

if _, err := io.ReadFull(p.bufReadConn, header[:]); err != nil {

return nil, err

}

sequence := header[3]

if sequence != p.sequence {

return nil, errors.New("")

}

p.sequence++

length := int(uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16)

data := make([]byte, length)

if p.readTimeout > 0 {

if err := p.bufReadConn.SetReadDeadline(time.Now().Add(p.readTimeout)); err != nil {

return nil, err

}

}

if _, err := io.ReadFull(p.bufReadConn, data); err != nil {

return nil, err

}

return data, nil

}

//写入

func (p *packetIO) writePacket(data []byte) error {

length := len(data) - 4

for length >= mysql.MaxPayloadLen {

data[0] = 0xff

data[1] = 0xff

data[2] = 0xff

data[3] = p.sequence

if n, err := p.bufWriter.Write(data[:4+mysql.MaxPayloadLen]); err != nil {

return mysql.ErrBadConn

} else if n != (4 + mysql.MaxPayloadLen) {

return mysql.ErrBadConn

} else {

p.sequence++

length -= mysql.MaxPayloadLen

data = data[mysql.MaxPayloadLen:]

}

}

data[0] = byte(length)

data[1] = byte(length >> 8)

data[2] = byte(length >> 16)

data[3] = p.sequence

if n, err := p.bufWriter.Write(data); err != nil {

//terror.Log(errors.Trace(err))

return err

} else if n != len(data) {

return mysql.ErrBadConn

} else {

p.sequence++

return nil

}

}

```

然后在包的基础上处理包内容,从服务端角度出发,发送握手初始化包、接受认证包、发送ok、接受命令内容。

```go

//1 发送初始化握手包

_ = conn.writeInitialHandshake()

//作为示例不支持老版本

var resp handshakeResponse41

var pos int

 //2 接受认证信息

data, err := conn.pkt.readPacket()

if err != nil {

log.Println(err.Error())

}

capability := uint32(binary.LittleEndian.Uint16(data[:2]))

 //按照协议内容取数据

pos, err = parseHandshakeResponseHeader(context.Background(), &resp, data)

err = parseHandshakeResponseBody(context.Background(), &resp, data, pos)

conn.capability = resp.Capability & conn.server.capability

conn.user = resp.User

conn.dbname = resp.DBName

conn.collation = resp.Collation

conn.attrs = resp.Attrs

data1 := make([]byte, 4, 32)

data1 = append(data, mysql.OKHeader)

data1 = append(data, 0, 0)

if conn.capability&mysql.ClientProtocol41 > 0 {

data1 = dumpUint16(data, mysql.ServerStatusAutocommit)

data1 = append(data, 0, 0)

}

 //3 认证成功

err = conn.pkt.writePacket(data1)

if err != nil {

panic(err)

}

conn.pkt.sequence = 0

err = conn.pkt.flush()

if err != nil {

panic(err)

}

 //4 接受命令

data2, err := conn.pkt.readPacket()

if err != nil {

panic(err)

}

cmd := data2[0]

data2 = data2[1:]

 //两个示例

switch cmd {

case mysql.ComQuery:

if len(data2) > 0{

dataStr := string(String(data2))

conn.handleQuery(dataStr)

}

case mysql.ComPing:

conn.writeOK()

}

```

分析查询

常规手法是使用yacc或者antlr4,我们直接使用TiDB的解析器,解析器部分就是对SQL递归下降解析,找到合适的生成式,不过就算是上下文无关文法,也得向左预取k个字节才能知道该走哪个case,因为SQL是上下文相关文法,上下文相关文法是可以转成多个上下文无关文法的,所以yacc里我们定义了BNF的语法规则(瞎说的)。

逻辑查询🔍计划

通过词法和语法分析后,我们拿到SQL语句的AST,之后我们需要将其转换为逻辑查询计划,将AST中FROM、Field、WHERE等子节点转换为连接、投影、选择等关系代数运算符组成的表达式树,在之后通过代数定律改进这个表达式树,这就就是逻辑优化。

用于改进查询计划的代数定律

- 交换律和结合律

- 用于选择的定律

- 选择下推

- 用于连接和积的定律

- 用于投影的定律

- 消除重复

- 用于分组和聚集的定律

上面是理论上的逻辑优化定律,以TiDB为例,我们看下它进行了哪些逻辑优化:

```go

var optRuleList = []logicalOptRule{

&columnPruner{},  

&buildKeySolver{},

&aggregationEliminator{},

&projectionEliminator{},

&maxMinEliminator{},

&ppdSolver{},

&outerJoinEliminator{},

&aggregationPushDownSolver{},

&pushDownTopNOptimizer{},

&joinReOrderSolver{},

}

```

物理查询计划

已经对一个查询进行了语法分析并将之转换成了一个逻辑查询计划,下一步我们需要将逻辑计划转换为物理计划,

医院回来再写

执行计划

其他

事务的概念最早可以在Jim Gray的论文 'The Transaction Concept: Virtues and Limitations' 中看到,RDBMS这块是离不开这个喜欢开船的大佬的,他搞了RDBMS中几乎所有最难的部分:数据完整性、安全性、故障恢复、并行性,保障RDBMS能够被市场接受。

论文中他提出Transaction的概念有以下属性:

- Consistency:交易要遵守法律(这里理解交易前后数据的完整性约束不能破坏,完整性包含关系的完整性和业务的完整性)

- Atomicity:即著名的all or nothing

- Durability:交易一旦提交就不可废弃

为了保障这些特性,论文论述了一些方案,备份进程、Redo、Undo,分析了update in place等,很多至今还在沿用。

需要注意的是ACID并不正交

文章转载自小张的科学日记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论