【源码阅读】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使用差异,一点小小的切换可能导致不断频繁建立连接与关闭连接等,最后我们也根据实际的情况提出解决此问题的方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/319216.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分层图像金字塔变压器

文章来源&#xff1a;hierarchical-image-pyramid-transformers 2024 年 2 月 5 日 本文介绍了分层图像金字塔变换器 (HIPT)&#xff0c;这是一种新颖的视觉变换器 (ViT) 架构&#xff0c;设计用于分析计算病理学中的十亿像素全幻灯片图像 (WSI)。 HIPT 利用 WSI 固有的层次结…

JDK14特性

JDK14 1 概述2 语法层面的变化1_instanceof的模式匹配(预览)2_switch表达式(标准)3_文本块改进(第二次预览)4_Records 记录类型(预览 JEP359) 3 API层面的变化4 关于GC1_G1的NUMA内存分配优化2_弃用SerialCMS,ParNewSerial Old3_删除CMS4_ZGC on macOS and Windows 4 其他变化1…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-8.2-链接脚本

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

3.9设计模式——Strategy 策略模式(行为型)

意图 定义一系列的算法&#xff0c;把它们一个个封装起来&#xff0c;并且使他们可以相互替换此模式使得算法可以独立于使用它们的客户而变化 结构 Strategy&#xff08;策略&#xff09;定义所有支持的算法的公共入口。Context使用这个接口来调用某ConcreteStrategy定义的方…

实验14 MVC

二、实验项目内容&#xff08;实验题目&#xff09; 编写代码&#xff0c;掌握MVC的用法。【参考课本 例1 】 三、源代码以及执行结果截图&#xff1a; example7_1.jsp&#xff1a; <% page contentType"text/html" %> <% page pageEncoding "ut…

【信息收集-基于字典爆破敏感目录--御剑/dirsearch

两个工具都是内置字典来对于目录进行爆破的&#xff0c;这是信息收集的一部分&#xff0c;若能在列举出的目录中找到有价值的信息能为后续渗透做准备。 御剑比较简便 dirsearch需要集成python3.x环境&#xff0c;但是可选的命令更多。两者爆破的结果不一定相同&#xff0c;可以…

怎样建设网站

建设一个网站需要经过一系列的步骤和技术&#xff0c;以下是一个简单的指导&#xff1a; 1. 确定网站目的&#xff1a;首先要确定网站的目的和目标。是为了促销产品&#xff1f;提供信息&#xff1f;还是为了社交交流&#xff1f;确定网站目的可以帮助你更好地规划网站的结构和…

【深度学习】位置编码

一、引言 Self-Attention并行的计算方式未考虑输入特征间的位置关系&#xff0c;这对NLP来说是不可接受的&#xff0c;毕竟一个句子中每个单词都有着明显的顺序关系。Transformer没有RNN、LSTM那样的顺序结构&#xff0c;所以Transformer在提出Self-Attention的同时提出了Posi…

RKNN Toolkit2 工具的使用

RKNN Toolkit2 是由瑞芯微电子 (Rockchip) 开发的一套用于深度学习模型优化和推理的工具。它主要面向在瑞芯微SoC上进行AI应用开发&#xff0c;但也可以用于PC平台进行模型的转换、量化、推理等操作。它支持将多种深度学习框架的模型&#xff08;如Caffe, TensorFlow, PyTorch等…

Linux下软硬链接和动静态库制作详解

目录 前言 软硬链接 概念 软链接的创建 硬链接的创建 软硬链接的本质区别 理解软链接 理解硬链接 小结 动静态库 概念 动静态库的制作 静态库的制作 动态库的制作 前言 本文涉及到inode和地址空间等相关概念&#xff0c;不知道的小伙伴可以先阅读以下两篇文章…

网络安全是智能汽车下一个要卷的方向?

2024年一季度&#xff0c;中国汽车市场延续了2023年的风格&#xff0c;核心就是「卷」。 2023年&#xff0c;我国汽车市场爆发「最强价格战」&#xff0c;燃油车的市场空间不断被挤压&#xff0c;如今只剩下最后一口气。近日乘联会发布4月1-14日最新数据&#xff0c;新能源&am…

【前端】VUE项目创建

在所需文件夹中打开cmd命令行窗口&#xff0c;输入vue ui 进入web可视化界面选择创建新项目 根据需求依次完成下列选择&#xff0c;下列是参考配置&#xff0c;完成后点击创建项目即可 最终显示完成

CUDA和显卡驱动

1.安装显卡驱动 https://www.nvidia.com/download/index.aspx?langen-us 由于我的显卡是RTX4060&#xff0c;因此先选择RTX40系列&#xff0c;然后选择RTX4060&#xff0c;进行安装 2.查看显卡对应的CUDA CUDA安装地址&#xff1a;https://developer.nvidia.com/cuda-toolk…

应用分层和企业规范

目录 一、应用分层 1、介绍 &#xff08;1&#xff09;为什么需要应用分层&#xff1f; &#xff08;2&#xff09;如何分层&#xff1f;&#xff08;三层架构&#xff09; MVC 和 三层架构的区别和联系 高内聚&#xff1a; 低耦合&#xff1a; 2、代码重构 controlle…

【 书生·浦语大模型实战营】学习笔记(六):Lagent AgentLego 智能体应用搭建

&#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料&#xff0c;配有全面而有深度的专栏内容&#xff0c;包括不限于 前沿论文解读、…

相机知识的补充

一&#xff1a;镜头 1.1MP的概念 相机中MP的意思是指百万像素。MP是mega pixel的缩写。mega意为一百万&#xff0c;mega pixel 指意为100万像素。“像素”是相机感光器件上的感光最小单位。就像是光学相机的感光胶片的银粒一样&#xff0c;记忆在数码相机的“胶片”&#xff…

spring boot运行过程中动态加载Controller

1.被加载的jar代码 package com.dl;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class App {public static void main(String[] args) {SpringApplication.run(A…

C++函数模板

简介&#xff1a;C函数模板的作用就是按照程序员的要求生成想要的函数对象。本质上是一种函数声明&#xff0c;在程序运行时依靠指定的参数类型由编译器临时生成函数对象。 1、auto自动类型推导 auto关键字可以取代变量声明时的函数类型&#xff0c;其实实际上会由编译器帮你把…

智能私信神器,转化率飙升的秘密!

在当今信息爆炸的时代&#xff0c;企业和商家面临着巨大的竞争压力&#xff0c;如何有效地吸引潜在客户、提高客户转化率成为摆在每一位市场营销人员面前的难题。随着人工智能技术的不断发展&#xff0c;智能私信软件应运而生&#xff0c;为企业提供了一个高效、便捷的解决方案…

前端发起网络请求的几种常见方式(XMLHttpRequest、FetchApi、jQueryAjax、Axios)

摘要 前端发起网络请求的几种常见方式包括&#xff1a; XMLHttpRequest (XHR)&#xff1a; 这是最传统和最常见的方式之一。它允许客户端与服务器进行异步通信。XHR API 提供了一个在后台发送 HTTP 请求和接收响应的机制&#xff0c;使得页面能够在不刷新的情况下更新部分内容…