文档章节

MySql协议详解-CRUD与Result篇

无毁的湖光-Al
 无毁的湖光-Al
发布于 2017/02/08 16:21
字数 1045
阅读 1198
收藏 39

MySql协议详解-CRUD与Result篇

Com_query报文

一般对DB的CRUD操作都由com_query报文封装并发送给DB。com_query报文如下图所示: packet
PacketLength:3byte表示body长度,防"粘包"。
sequenceId:1byte防串包。
body部分: 首先是1byte的command,代表是quey、initdb或者quit等,在此只讨论query的情况。 然后一个以0x00结尾的字符串,这个字符串就是想要执行的SQL,实际操作中即是一直读body直到读到一个0x00皆为的字符串即停止。

insert,update和delete

如果SQL是insert、update或者是delete,则返回的是对应的okay或者error报文。
okay报文的类表示:

public class OkPacket extends MySQLPacket {
    public static final byte FIELD_COUNT = 0x00;
    public static final byte[] OK = new byte[] { 7, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0 };
    public static final byte[] AUTH_OK = new byte[] { 7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0 };
    public byte fieldCount = FIELD_COUNT;
    public long affectedRows;
    public long insertId;
    public int serverStatus;
    public int warningCount;
    public byte[] message;
}

error报文的类表示:

public class ErrorPacket extends MySQLPacket {
    public static final byte FIELD_COUNT = (byte) 0xff;
    private static final byte SQLSTATE_MARKER = (byte) '#';
    private static final byte[] DEFAULT_SQLSTATE = "HY000".getBytes();

    public byte fieldCount = FIELD_COUNT;
    public int errno;
    public byte mark = SQLSTATE_MARKER;
    public byte[] sqlState = DEFAULT_SQLSTATE;
    public byte[] message;
}

Select和ResultSet报文

如果执行的SQL是select语句,则返回的报文比较复杂,不过笔者已经整理成图的形式。

Row报文

首先ResultSet是由很多行(Row)组成,每一行(Row)就表示了一条记录,Row格式如下所示:
rowpacket
每一行(row)又分好field_count个字段,这个field_count将会在比Row还高一层的Result格式中描述,下面有详解。
每一个字段都是一个length-value对,length长度是3byte,其读取方法很特殊,现在直接用代码表述:

    public int readUB3() {
        final byte[] b = this.data;
        int i = b[position++] & 0xff;
        i |= (b[position++] & 0xff) << 8;
        i |= (b[position++] & 0xff) << 16;
        return i;
    }

获取了length长度之后,则可以根据此读出来后面的value。value的类型也会在后面的Resutl格式中描述。

ResultSet格式

严格来说ResultSet是由多个独立的报文以协议的形式组织起来,现直接放出ResultSet的协议格式图: resultset
从上图中可以看到,当客户端发送一个select的com_query包后,DB会按照下列步骤返回:
Step1:返回一个ResultSetHeader报文,其中包含了fieldCount,在此图就不例出了,现只给出代码定义。

public class ResultSetHeaderPacket extends MySQLPacket {
    public int fieldCount;
    public long extra;
}

Step2:根据读取到的fieldCount来在接下来的byte流里面读取fieldCount个FiledPacket报文,FieldPacket报文的代码定义:

public class FieldPacket extends MySQLPacket {
    private static final byte[] DEFAULT_CATALOG = "def".getBytes();
    private static final byte[] FILLER = new byte[2];

    public byte[] catalog = DEFAULT_CATALOG;
    public byte[] db;
    public byte[] table;
    public byte[] orgTable;
    public byte[] name;
    public byte[] orgName;
    public int charsetIndex;
    public long length;
    public int type;
    public int flags;
    public byte decimals;
    public byte[] definition;
}

具体逻辑请参照github
Step3:再读取一个eof包表示field包流的结束
eof包的代码定义:

public class EOFPacket extends MySQLPacket {
    public static final byte FIELD_COUNT = (byte) 0xfe;

    public byte fieldCount = FIELD_COUNT;
    public int warningCount;
    public int status = 2;

Step4:一直读Row,直到读到last eof位置,Row格式已经在上面给过。
Step5:如果读到任何一个error包后,此此读取结束,抛出错误。
Step6:值得注意的是,如果eof中的status & SERVER_MORE_RESULT_EXISTS不为0,表明还有ResultSet。则继续返回到fieldCount阶段进行下一步的读取。
Step7:至此,整个ResultSet读取完毕。
下面给出上述过程的java代码(基于Netty):

private boolean handleResultSet(BinaryPacket bin, CmdType cmdType) {
        boolean result = false;
        int type = bin.data[0];
        switch (type) {
            case ErrorPacket.FIELD_COUNT:
                // 重置状态,且告诉上层当前select已经处理完毕
                resetSelect();
                result = true;
                ErrorPacket err = new ErrorPacket();
                err.read(bin);
                // write(bin,cmdType);
                getResponseHandler().errorResponse(bin);
                logger.error("handleResultSet errorMessage:" + new String(err.message));
                break;
            case EOFPacket.FIELD_COUNT:
                EOFPacket eof = new EOFPacket();
                eof.read(bin);
                if (selectState == BackendConnState.RESULT_SET_FIELDS) {
                    // logger.info("eof");
                    // 推进状态 需要步进两次状态,先到field_eof,再到row
                    selectStateStep();
                    selectStateStep();
                    // 给FieldList增加eof
                    addToFieldList(bin);
                    getResponseHandler().fieldListResponse(fieldList);
                } else {
                    if (eof.hasStatusFlag(MySQLPacket.SERVER_MORE_RESULTS_EXISTS)) {
                        // 重置为select的初始状态,但是还是处在select mode下
                        selectState = BackendConnState.RESULT_SET_FIELD_COUNT;
                    } else {
                        // 重置,且告诉上层当前select已经处理完毕
                        resetSelect();
                        result = true;
                    }
                    getResponseHandler().lastEofResponse(bin);
                }
                break;
            default:
                switch (selectState) {
                    case BackendConnState.RESULT_SET_FIELD_COUNT:
                        selectStateStep();
                        addToFieldList(bin);
                        break;
                    case BackendConnState.RESULT_SET_FIELDS:
                        addToFieldList(bin);
                        break;
                    case BackendConnState.RESULT_SET_ROW:
                        getResponseHandler().rowResponse(bin);
                        break;
                }
        }
        return result;
    }

GitHub链接

https://github.com/alchemystar/Lancelot.git

原文链接

https://my.oschina.net/alchemystar/blog/834150

© 著作权归作者所有

无毁的湖光-Al

无毁的湖光-Al

粉丝 437
博文 30
码字总数 51882
作品 0
浦东
后端工程师
私信 提问
加载中

评论(6)

无毁的湖光-Al
无毁的湖光-Al 博主

引用来自“昨夜今夕”的评论

引用来自“昨夜今夕”的评论

大佬,你要给我们看的图片可以给我看一下吗,现在看不到了

引用来自“无毁的湖光-Al”的评论

我这边能看到啊,清空缓存也能看
本来是看不到的,看到的全部是开源中国的logo,我看到你说的,清空了缓存就可以看到了

@昨夜今夕 ^_^
昨夜今夕

引用来自“昨夜今夕”的评论

大佬,你要给我们看的图片可以给我看一下吗,现在看不到了

引用来自“无毁的湖光-Al”的评论

我这边能看到啊,清空缓存也能看
本来是看不到的,看到的全部是开源中国的logo,我看到你说的,清空了缓存就可以看到了
无毁的湖光-Al
无毁的湖光-Al 博主

引用来自“昨夜今夕”的评论

大佬,你要给我们看的图片可以给我看一下吗,现在看不到了
我这边能看到啊,清空缓存也能看
昨夜今夕
大佬,你要给我们看的图片可以给我看一下吗,现在看不到了
无毁的湖光-Al
无毁的湖光-Al 博主

引用来自“路易666”的评论

6
😆
叫那扬清风
叫那扬清风
6
泥沙砖瓦浆木匠/springboot-learning-example

springboot-learning-example spring boot 实践学习案例,是 spring boot 初学者及核心技术巩固的最佳实践。 推荐 springcloud-learning-example spring cloud 实践学习案例 https://github...

泥沙砖瓦浆木匠
2017/03/29
0
0
博客阅读整理一

(部分文章是系列文章,可在原文博客查找) Java ClassLoader, JavaAgent, Aspectj Weaving一站式扫盲帖:主要总结了ClassLoader、java代理、反射相关的知识点 jdbc实现篇-源码:介绍了jdbc的源...

oO脾气不坏Oo
2015/10/17
91
0
Linux 系统运维学习方法汇总

本文转载自:http://freeloda.blog.51cto.com/2033581/1315694 大纲 一、前言 二、Linux 运维大环境说明 三、Linux 运维学习思路 四、Linux 运维大方向说明 五、Linux 运维学习必看书籍推荐 ...

woshiliwentong
2014/03/18
0
0
jQuery EasyUI使用教程之构建CRUD应用程序

CRUD应用程序已经成为一个常见的收集数据并且正确管理数据的Web应用程序。CRUD允许我们生成页面列表并且可以编辑数据库记录。本文主要为大家展示如何利用jQuery EasyUI框架来实现CRUD应用程序...

Miss_Hello_World
2015/10/09
84
0
MySQL数据库实操教程(35)——完结篇

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 https://blog.csdn.net/lfdfhl/article/details/99707521 自定义View系列教程00–推翻自己和...

谷哥的小弟
08/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

springboot 403 问题

添加WebAppConfigurer 配置 @Configuration@EnableAutoConfigurationpublic class WebAppConfigurer extends WebMvcConfigurerAdapter { public WebAppConfigurer() { } ......

布袋和尚_爱吃鱼
5分钟前
1
0
Python自动更换壁纸爬虫与tkinter结合

直接上代码 import ctypesimport timeimport requestsimport osfrom threading import Threadfrom tkinter import Tk, Label, Button,Entry,StringVar,messagebox# '放到AppData\Roami......

物种起源-达尔文
6分钟前
1
0
Postgresql Study 笔记

Postgresql 安装 Windows, MAC Install Postgresql 下载地址: https://www.enterprisedb.com/downloads/postgres-postgresql-downloads Linux Install sudo apt-get update sudo apt-get in......

slagga
7分钟前
1
0
layer.open 打开新页面传参问题

如图所示,点击出售,把A页面的数据传到弹框上面,因为弹框比较复杂,所以使用引入一个新页面。 A.html a.js B.html b.js 1、第一种方案 sellInte: function (){ var obj = document.g...

木九天
10分钟前
1
0
沙龙报名 | 区块链数据服务技术应用实践

京东云是国内首家提供区块链数据在线分析服务产品的公司,也是行业内首家对区块链数据服务进行开源的公司。 本次沙龙是京东云BDS开源后,首次在深圳举办线下沙龙,我们将邀请京东云BDS团队核...

京东云技术新知
11分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部