当前位置: 首页 > news >正文

【源码阅读】Golang中的go-sql-driver库源码探究

文章目录

    • 前言
    • 一、go-sql-driver/mysql
      • 1、驱动注册:sql.Register
      • 2、驱动实现:MysqlDriver
      • 3、RegisterDialContext
    • 二、总结

前言

在上篇文章中我们知道,database/sql只是提供了驱动相关的接口,并没有相关的具体实现,具体内容是由第三方实现的,如go-sql-driver/mysql:https://github.com/go-sql-driver/mysql/,本章中我们主要是探究这个驱动实现库的具体实现。以及它是如何与database/sql一起作用的。

一、go-sql-driver/mysql

go-sql-driver作为一个三方驱动库,主要就是实现database/sql中的驱动接口了,因此,主要的文件也就是driver.go、connector.go和connection.go几个文件了。因此,本章的阅读业主要聚焦与这三个文件中的源码内容。
在这里插入图片描述

1、驱动注册:sql.Register

通常,我们都会这样调用database/sql的Open方法创建一个db实例:

import ("database/sql"_ "github.com/go-sql-driver/mysql"
)// ...db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {panic(err)
}

初看是不是觉得很奇怪,在这段代码中,我们没有直接使用到go-sql-driver的的任何东西,但却需要引入这个包,这是因为,sql.Open方法中,我们知道,会检查获取对应的驱动,而驱动的注册是由第三方驱动实现包调用Register方法完成的。

在go-sql-driver中的driver.go中,我们发现init函数中会调用Register方法注册相应的驱动,这也是上面的代码中为什么需要引入这个包的原因。

func init() {if driverName != "" {sql.Register(driverName, &MySQLDriver{})}
}

2、驱动实现:MysqlDriver

在go-sql-driver中,核心的driver.go中实现了具体的mysql驱动(MysqlDriver)

// Open new Connection.
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
// the DSN string is formatted
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {cfg, err := ParseDSN(dsn)if err != nil {return nil, err}c := newConnector(cfg)return c.Connect(context.Background())
}

在该方法中,首先从数据源dsn中解析出对应的配置,然后再构造对应的连接器,调用连接器的Connect方法与mysql建立连接。

connector实现了driver.Connector接口,其中Connect方法主要是与mysql进行交互,包括:拨号(dial)、认证、利用mysql协议发包与收包处理结果等,

type connector struct {cfg               *Config // immutable private copy.encodedAttributes string  // Encoded connection attributes.
}func newConnector(cfg *Config) *connector {encodedAttributes := encodeConnectionAttributes(cfg)return &connector{cfg:               cfg,encodedAttributes: encodedAttributes,}
}// Connect implements driver.Connector interface.
// Connect returns a connection to the database.
func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {var err error// Invoke beforeConnect if present, with a copy of the configurationcfg := c.cfgif c.cfg.beforeConnect != nil {cfg = c.cfg.Clone()err = c.cfg.beforeConnect(ctx, cfg)if err != nil {return nil, err}}// New mysqlConnmc := &mysqlConn{maxAllowedPacket: maxPacketSize,maxWriteSize:     maxPacketSize - 1,closech:          make(chan struct{}),cfg:              cfg,connector:        c,}mc.parseTime = mc.cfg.ParseTime// Connect to ServerdialsLock.RLock()dial, ok := dials[mc.cfg.Net]dialsLock.RUnlock()if ok {dctx := ctxif mc.cfg.Timeout > 0 {var cancel context.CancelFuncdctx, cancel = context.WithTimeout(ctx, c.cfg.Timeout)defer cancel()}mc.netConn, err = dial(dctx, mc.cfg.Addr)} else {nd := net.Dialer{Timeout: mc.cfg.Timeout}mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)}if err != nil {return nil, err}mc.rawConn = mc.netConn// Enable TCP Keepalives on TCP connectionsif tc, ok := mc.netConn.(*net.TCPConn); ok {if err := tc.SetKeepAlive(true); err != nil {c.cfg.Logger.Print(err)}}// Call startWatcher for context support (From Go 1.8)mc.startWatcher()if err := mc.watchCancel(ctx); err != nil {mc.cleanup()return nil, err}defer mc.finish()mc.buf = newBuffer(mc.netConn)// Set I/O timeoutsmc.buf.timeout = mc.cfg.ReadTimeoutmc.writeTimeout = mc.cfg.WriteTimeout// Reading Handshake Initialization PacketauthData, plugin, err := mc.readHandshakePacket()if err != nil {mc.cleanup()return nil, err}if plugin == "" {plugin = defaultAuthPlugin}// Send Client Authentication PacketauthResp, err := mc.auth(authData, plugin)if err != nil {// try the default auth plugin, if using the requested plugin failedc.cfg.Logger.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())plugin = defaultAuthPluginauthResp, err = mc.auth(authData, plugin)if err != nil {mc.cleanup()return nil, err}}if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {mc.cleanup()return nil, err}// Handle response to auth packet, switch methods if possibleif err = mc.handleAuthResult(authData, plugin); err != nil {// Authentication failed and MySQL has already closed the connection// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).// Do not send COM_QUIT, just cleanup and return the error.mc.cleanup()return nil, err}if mc.cfg.MaxAllowedPacket > 0 {mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket} else {// Get max allowed packet sizemaxap, err := mc.getSystemVar("max_allowed_packet")if err != nil {mc.Close()return nil, err}mc.maxAllowedPacket = stringToInt(maxap) - 1}if mc.maxAllowedPacket < maxPacketSize {mc.maxWriteSize = mc.maxAllowedPacket}// Handle DSN Paramserr = mc.handleParams()if err != nil {mc.Close()return nil, err}return mc, nil
}// Driver implements driver.Connector interface.
// Driver returns &MySQLDriver{}.
func (c *connector) Driver() driver.Driver {return &MySQLDriver{}
}

同时,我们还注意到,Connect方法中调用了一个startWatcher方法,该方法从watcher通道中接收一个ctx,并对这个ctx进行监听,每次都会调用一个watchCancel方法将ctx传递Watcher,watcher监听到ctx.Done的信号后,将会调用cancel方法,启动清理工作。

func (mc *mysqlConn) startWatcher() {watcher := make(chan context.Context, 1)mc.watcher = watcherfinished := make(chan struct{})mc.finished = finishedgo func() {for {var ctx context.Contextselect {case ctx = <-watcher:case <-mc.closech:return}select {case <-ctx.Done():mc.cancel(ctx.Err())case <-finished:case <-mc.closech:return}}}()
}

cancel方法将会调用cleanup方法进行连接的清理工作,可以看到在cleanup中调用了conn.Close,将这个物理连接关闭掉。因此,我们在使用QueryContext或者ExecContext时候,如果ctx设置了超时时间,或者主动cancel,那么意味着这个连接将会被断掉。极端情况下,大量连接同时超时,意味着连接都将失效,此时再有新的请求打进来则会重新建立新的连接,会有一定的连接建立开销。由于连接池是database/sql维护的,因此这也只是客户端(或者说mysql sdk)层面的失效,mysql server接收到的sql执行是不会被中断的。

// finish is called when the query has canceled.
func (mc *mysqlConn) cancel(err error) {mc.canceled.Set(err)mc.cleanup()
}// Closes the network connection and unsets internal variables. Do not call this
// function after successfully authentication, call Close instead. This function
// is called before auth or on auth failure because MySQL will have already
// closed the network connection.
func (mc *mysqlConn) cleanup() {if mc.closed.Swap(true) {return}// Makes cleanup idempotentclose(mc.closech)conn := mc.rawConnif conn == nil {return}if err := conn.Close(); err != nil {mc.log(err)}// This function can be called from multiple goroutines.// So we can not mc.clearResult() here.// Caller should do it if they are in safe goroutine.
}

在实际项目中,为了减少使用层面的超时导致连接失效这种情况,我们也可以对mysql server设置一个wait_timeout时间,并且调用QueryContext/ExecContext的超时时间要小于这个wait_timeout时间,这样则不会由于某业务中有慢查的sql,导致ctx超时,从而频繁触发连接的重新建立。

3、RegisterDialContext

最后我们再看看下这个静态方法:RegisterDialContext,这个方法主要作用就是注册对应的协议的dialFunc,便于在进行数据库连接时候找到真正的地址。

// RegisterDialContext registers a custom dial function. It can then be used by the
// network address mynet(addr), where mynet is the registered new network.
// The current context for the connection and its address is passed to the dial function.
func RegisterDialContext(net string, dial DialContextFunc) {dialsLock.Lock()defer dialsLock.Unlock()if dials == nil {dials = make(map[string]DialContextFunc)}dials[net] = dial
}// DialContextFunc is a function which can be used to establish the network connection.
// Custom dial functions must be registered with RegisterDialContext
type DialContextFunc func(ctx context.Context, addr string) (net.Conn, error)

二、总结

本篇文章我们看了go-sql-driver的具体实现,整体上来说,go-sql-driver都是实现database/sql的driver.Driver接口,直接对接mysql服务端,支持mysql协议的收发包,在api层面,query/exec两个方法都提供了带ctx的方法,带ctx和不带ctx的api使用差异,一点小小的切换可能导致不断频繁建立连接与关闭连接等,最后我们也根据实际的情况提出解决此问题的方案。

相关文章:

【源码阅读】Golang中的go-sql-driver库源码探究

文章目录 前言一、go-sql-driver/mysql1、驱动注册&#xff1a;sql.Register2、驱动实现&#xff1a;MysqlDriver3、RegisterDialContext 二、总结 前言 在上篇文章中我们知道&#xff0c;database/sql只是提供了驱动相关的接口&#xff0c;并没有相关的具体实现&#xff0c;具…...

2024-05-08 postgres-火山模型-执行-记录

摘要: 2024-05-08 postgres-火山模型-执行-记录 上下文: 2024-05-08 postgres-调试及分析-记录-CSDN博客 火山模型: 数据流是在查询树上&#xff0c;自上而下进行拉取&#xff0c;由上而下的调用。树本身就表明了数据的流动。每次执行一个元组&#xff0c;也就类似于迭代器的…...

QT5带UI的常用控件

目录 新建工程&#xff0c;Qmainwindow带UI UI设计器 常用控件区 Buttons 按钮 containers 容器 控件属性区域 对象监视区 布局工具区 信号与槽区 简单例子1 放置一个按钮控件&#xff0c;改文本为发送&#xff0c;该按键为Button1&#xff1b; 按钮关联信号和…...

识货小程序逆向

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872&#xff0c;x30184483x…...

【OceanBase 系列】—— OceanBase v4.3 特性解读:查询性能提升之利器列存储引擎

原文链接&#xff1a;OceanBase 社区 对于分析类查询&#xff0c;列存可以极大地提升查询性能&#xff0c;也是 OceanBase 做好 HTAP 和 OLAP 的一项不可缺少的特性。本文介绍 OceanBase 列存的实现特色。 OceanBase从诞生起就一直坚持LSM-Tree架构&#xff0c;不断打磨功能支…...

【Java开发的我出书啦,各位同仁快过来围观】!!!

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容出书的目的出书的过程书籍的内容 &#x1f4e5;博主的话 &#x1f50a;博主介绍 文章目录 &#x1f50a;博主介绍&#x1f964;本文内容出书的目的出书的过程书籍的内容 &#x1f4e5;博主的话 &#x1f33e;阅读前&#x…...

AI预测福彩3D第10套算法实战化赚米验证第1弹2024年5月5日第1次测试

从今天开始&#xff0c;准备启用第10套算法&#xff0c;来验证下本算法的可行性。因为本算法通过近三十期的内测&#xff08;内测版没有公开预测结果&#xff09;&#xff0c;发现本算法的预测结果优于其他所有算法的效果。彩票预测只有实战才能检验是否有效&#xff0c;只有真…...

leetcode 2944.购买水果需要的最小金币

思路&#xff1a;dp 这道题一开始想的时候并不会&#xff0c;但是看到了有些水果可以买也可以不买&#xff0c;所以就想到了选择与不选择的思路。 对于每一个水果&#xff0c;我们都有买和不买的选择&#xff0c;但是我们的第一个水果是一定要买的。然后再往后推导。 用dp[]…...

算法人生(14):从“探索平衡策略”看“生活工作的平衡之道”

在强化学习中&#xff0c;有一种策略叫“探索平衡策略Exploration-Exploitation Trade-off&#xff09;”&#xff0c;这种策略的核心是在探索未知领域&#xff08;以获取更多信息&#xff09;和利用已知信息&#xff08;来最大化即时回报&#xff09;之间寻求平衡&#xff0c;…...

如何使用Tushare+ Backtrader进行股票量化策略回测

数量技术宅团队在CSDN学院推出了量化投资系列课程 欢迎有兴趣系统学习量化投资的同学&#xff0c;点击下方链接报名&#xff1a; 量化投资速成营&#xff08;入门课程&#xff09; Python股票量化投资 Python期货量化投资 Python数字货币量化投资 C语言CTP期货交易系统开…...

Guid转换为字符串

在理想情况下&#xff0c;任何计算机和计算机集群都不会生成两个相同的GUID。GUID 的总数达到了2128&#xff08;3.41038&#xff09;个&#xff0c;所以随机生成两个相同GUID的可能性非常小&#xff0c;但并不为0。GUID一词有时也专指微软对UUID标准的实现。 (1). GUID&#…...

OpenAI的搜索引擎要来了!

最近的报道和业界泄露信息显示&#xff0c;OpenAI正秘密研发一款新的搜索引擎&#xff0c;可能叫SearchGPT或Sonic&#xff0c;目标是挑战Google的搜索霸权。预计这款搜索引擎可能在5月9日即将到来的活动中正式亮相。 SearchGPT的蛛丝马迹 尽管OpenAI对SearchGPT尚未表态&…...

PaddlePaddle与OpenMMLab

产品全景_飞桨产品-飞桨PaddlePaddle OpenMMLab算法应用平台...

HBuilderX uniapp+vue3+vite axios封装

uniapp 封装axios 注&#xff1a;axios必须低于0.26.0&#xff0c;重中之重 重点&#xff1a;封装axios的适配器adapter 1.安装axios npm install axios0.26.0创建api文件夹 2.新建adapter.js文件 import settle from "axios/lib/core/settle" import buildURL…...

【网络安全产品】---应用防火墙(WAF)

what Web应用防火墙&#xff08;Web Application Firewall) WAF可对网站或者App的业务流量进行恶意特征识别及防护&#xff0c;在对流量清洗和过滤后&#xff0c;将正常、安全的流量返回给服务器&#xff0c;避免网站服务器被恶意入侵导致性能异常等问题&#xff0c;从而保障…...

C++学习第十二天(继承)

1、继承的概念以及定义 继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行拓展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序设计的层次结构&#x…...

WPF DataGrid绑定后端 在AutoGeneratingColumn事件中改变列名

public void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e){var propertyDescriptor (PropertyDescriptor)e.PropertyDescriptor;if (propertyDescriptor.IsBrowsable){e.Column.Header propertyDescriptor.DisplayName;}else{e.Cancel true;}}实体类中…...

2024 CorelDraw最新图形设计软件 激活安装教程来了

2024年3月&#xff0c;备受瞩目的矢量制图及设计软件——CorelDRAW Graphics Suite 2024 正式面向全球发布。这一重大更新不仅是 CorelDRAW 在 36 年创意服务历史中的又一重要里程碑&#xff0c;同时也展现了其在设计软件领域不断创新和卓越性能的领导地位。 链接: https://pan…...

双网口扩展IO支持8DO输出

M320E以太网远程I/O数据采集模块是一款工业级、隔离设计、高可靠性、高稳定性和高精度数据采集模块&#xff0c;嵌入式32位高性能微处理器MCU&#xff0c;集成2路工业10/100M自适应以太网模块里面。提供多种I/O&#xff0c;支持标准Modbus TCP&#xff0c;可集成到SCADA、OPC服…...

【负载均衡在线OJ项目日记】编译与日志功能开发

目录 日志功能开发 常见的日志等级 日志功能代码 编译功能开发 创建子进程和程序替换 重定向 编译功能代码 日志功能开发 日志在软件开发和运维中起着至关重要的作用&#xff0c;目前我们不谈运维只谈软件开发&#xff1b;日志最大的作用就是用于故障排查和调试&#x…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...

规则与人性的天平——由高考迟到事件引发的思考

当那位身着校服的考生在考场关闭1分钟后狂奔而至&#xff0c;他涨红的脸上写满绝望。铁门内秒针划过的弧度&#xff0c;成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定"&#xff0c;构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...