最近在整理关系型数据库的内容,这是第三篇本来计划中的是查询编译,但是有涉及到一些进程模型、词法语法分析、查询执行等的内容,就改成了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并不正交




