网站首页 > 文章中心 > 其它

go语言中进行子查询

作者:小编 更新时间:2023-10-09 19:26:18 浏览量:142人看过

SQL查询语句中,什么叫子查询?

嵌套SELECT语句也叫子查询,一个 SELECT 语句的查询结果能够作为另一个语句的输入值.子查询不但能够出现在Where子句中,也能够出现在from子句中,作为一个临时表使用,也能够出现在select list中,作为一个字段值来返回.

①.、单行子查询 :单行子查询是指子查询的返回结果只有一行数据.当主查询语句的条件语句中引用子查询结果时可用单行比较符号(=, , , =, =, )来进行比较.

例:

select ename,deptno,sal?

from emp?

where deptno=(select deptno from dept where loc='NEW YORK');?

①.).多行子查询使用IN操作符号例子:查询选修了老师名叫Rona(假设唯一)的学生名字

sql select stName

Golang 中更好的错误处理:理论和实践技巧

云和安全管理服务专家新钛云服 张春翻译

这种方法有几个缺点.首先,它可以对程序员隐藏错误处理路径,特别是在捕获异常不是强制性的情况下,例如在 Python 中.即使在具有必须处理的 Java 风格的检查异常的语言中,如果在与原始调用不同的级别上处理错误,也并不总是很明显错误是从哪里引发的.

我们都见过长长的代码块包装在一个 try-catch 块中.在这种情况下,catch 块实际上充当 goto 语句,这通常被认为是有害的(奇怪的是,C 中的关键字被认为可以接受的少数用例之一是错误后清理,因为该语言没有 Golang- 样式延迟语句).

如果你确实从源头捕获异常,你会得到一个不太优雅的 Go 错误模式版本.这可能会解决混淆代码的问题,但会遇到另一个问题:性能.在诸如 Java 之类的语言中,抛出异常可能比函数的常规返回慢数百倍.

Java 中最大的性能成本是由打印异常的堆栈跟踪造成的,这是昂贵的,因为运行的程序必须检查编译它的源代码 .仅仅进入一个 try 块也不是空闲的,因为需要保存 CPU 内存寄存器的先前状态,因为它们可能需要在抛出异常的情况下恢复.

如果您将异常视为通常不会发生的异常情况,那么异常的缺点并不重要.这可能是传统的单体应用程序的情况,其中大部分代码库不必进行网络调用——一个操作格式良好的数据的函数不太可能遇到错误(除了错误的情况).一旦您在代码中添加 I/O,无错误代码的梦想就会破灭:您可以忽略错误,但不能假装它们不存在!

try {

doSometing()

} catch (IOException e) {

// ignore it

}

与大多数其他编程语言不同,Golang 接受错误是不可避免的. 如果在单体架构时代还不是这样,那么在今天的模块化后端服务中,服务通常和外部 API 调用、数据库读取和写入以及与其他服务通信 .

以上所有方法都可能失败,解析或验证从它们接收到的数据(通常在无模式 JSON 中)也可能失败.Golang 使可以从这些调用返回的错误显式化,与普通返回值的等级相同.从函数调用返回多个值的能力支持这一点,这在大多数语言中通常是不可能的.Golang 的错误处理系统不仅仅是一种语言怪癖,它是一种将错误视为替代返回值的完全不同的方式!

重复 if err != nil

对 Go 错误处理的一个常见批评是被迫重复以下代码块:

res, err := doSomething()

if err != nil {

// Handle error

这么多行代码!这么低效!如果您认为上述内容不优雅或浪费代码,您可能忽略了我们检查代码中的错误的全部原因:我们需要能够以不同的方式处理它们!对 API 或数据库的调用可能会被重试.

有时事件的顺序很重要:调用外部 API 之前发生的错误可能不是什么大问题(因为数据从未通过发送),而 API 调用和写入本地数据库之间的错误可能需要立即注意,因为 这可能意味着系统最终处于不一致的状态.即使我们只想将错误传播给调用者,我们也可能希望用失败的解释来包装它们,或者为每个错误返回一个自定义错误类型.

并非所有错误都是相同的,并且向调用者返回适当的错误是 API 设计的重要部分,无论是对于内部包还是 REST API .

不必担心在你的代码中重复 if err != nil ——这就是 Go 中的代码应该看起来的样子.

自定义错误类型和错误包装

从导出的方法返回错误时,请考虑指定自定义错误类型,而不是单独使用错误字符串.字符串在意外代码中是可以的,但在导出的函数中,它们成为函数公共 API 的一部分.更改错误字符串将是一项重大更改——如果没有明确的错误类型,需要检查返回错误类型的单元测试将不得不依赖原始字符串值!事实上,基于字符串的错误也使得在私有方法中测试不同的错误案例变得困难,所以呢您也应该考虑在包中使用它们.回到错误与异常的争论,返回错误也使代码比抛出异常更容易测试,因为错误只是要检查的返回值.不需要测试框架或在测试中捕获异常 .

可以在 database/sql 包中找到简单自定义错误类型的一个很好的示例.它定义了一个导出常量列表,表示包可以返回的错误类型,最著名的是 sql.ErrNoRows.虽然从 API 设计的角度来看,这种特定的错误类型有点问题(您可能会争辩说 API 应该返回一个空结构而不是错误),但任何需要检查空行的应用程序都可以导入该常量并在代码中使用它不必担心错误消息本身会改变和破坏代码.

对于更复杂的错误处理,您可以通过实现返回错误字符串的 Error() 方法来定义自定义错误类型.自定义错误可以包括元数据,例如错误代码或原始请求参数.如果您想表示错误类别,它们很有用.DigitalOcean 的本教程展示了如何使用自定义错误类型来表示可以重试的一类临时错误.

通常,错误会通过将低级错误与更高级别的解释包装起来,从而在程序的调用堆栈中传播.例如,数据库错误可能会以下列格式记录在 API 调用处理程序中:调用 CreateUser 端点时出错:查询数据库时出错:pq:检测到死锁.这很有用,因为它可以帮助我们跟踪错误在系统中传播的过程,向我们展示根本原因(数据库事务引擎中的死锁)以及它对更广泛系统的影响(调用者无法创建新用户).

Panics

不要 panic()!长时间运行的应用程序应该优雅地处理错误而不是panic.即使在无法恢复的情况下(例如在启动时验证配置),最好记录一个错误并优雅地退出.panic比错误消息更难诊断,并且可能会跳过被推迟的重要关闭代码.

Logging

我还想简要介绍一下日志记录,因为它是处理错误的关键部分.通常你能做的最好的事情就是记录收到的错误并继续下一个请求.

除非您正在构建简单的命令行工具或个人项目,否则您的应用程序应该使用结构化的日志库,该库可以为日志添加时间戳,并提供对日志级别的控制.最后一部分特别重要,因为它将允许您突出显示应用程序记录的所有错误和警告.通过帮助将它们与信息级日志分开,这将为您节省无数时间.

微服务架构还应该在日志行中包含服务的名称以及机器实例的名称.默认情况下记录这些时,程序代码不必担心包含它们.您也可以在日志的结构化部分中记录其他字段,例如收到的错误(如果您不想将其嵌入日志消息本身)或有问题的请求或响应.只需确保您的日志没有泄露任何敏感数据,例如密码、API 密钥或用户的个人数据!

对于日志库,我过去使用过 logrus 和 zerolog,但您也可以选择其他结构化日志库.如果您想了解更多信息,互联网上有许多关于如何使用这些的指南.如果您将应用程序部署到云中,您可能需要日志库上的适配器来根据您的云平台的日志 API 格式化日志 - 没有它,云平台可能无法检测到日志级别等某些功能.

如果您在应用程序中使用调试级别日志(默认情况下通常不记录),请确保您的应用程序可以轻松更改日志级别,而无需更改代码.更改日志级别还可以暂时使信息级别甚至警告级别的日志静音,以防它们突然变得过于嘈杂并开始淹没错误.您可以使用在启动时检查以设置日志级别的环境变量来实现这一点.

原文:

GO语言学习系列八——GO函数(func)的声明与使用

GO是编译性语言,所以函数的顺序是无关紧要的,为了方便阅读,建议入口函数 main 写在最前面,其余函数按照功能需要进行排列

GO的函数 不支持嵌套,重载和默认参数

GO的函数 支持 无需声明变量,可变长度,多返回值,匿名,闭包等

GO的函数用 func 来声明,且左大括号 { 不能另起一行

一个简单的示例:

输出为:

参数:可以传0个或多个值来供自己用

返回:通过用 return 来进行返回

上面就是一个典型的多参数传递与多返回值

对例子的说明:

按值传递:是对某个变量进行复制,不能更改原变量的值

在上例中,返回值 (d int, e int, f int) { 是进行了命名,如果不想命名可以写成 (int,int,int){ ,返回的结果都是一样的,但要注意:

当返回了多个值,我们某些变量不想要,或实际用不到,我们可以使用 _ 来补位,例如上例的返回我们可以写成 d,_,f := test(a,b,c) ,我们不想要中间的返回值,可以以这种形式来舍弃掉

在参数后面以 变量 ... type 这种形式的,我们就要以判断出这是一个可变长度的参数

在上例中, strs ...string 中, strs 的实际值是b,c,d,e,这就是一个最简单的传递可变长度的参数的例子,更多一些演变的形式,都非常类似

在GO中 defer 关键字非常重要,相当于面相对像中的析构函数,也就是在某个函数执行完成后,GO会自动这个;

如果在多层循环中函数里,都定义了 defer ,那么它的执行顺序是先进后出;

当某个函数出现严重错误时, defer 也会被调用

输出为

这是一个最简单的测试了,当然还有更复杂的调用,比如调试程序时,判断是哪个函数出了问题,完全可以根据 defer 打印出来的内容来进行判断,非常快速,这种留给你们去实现

一个函数在函数体内自己调用自己我们称之为递归函数,在做递归调用时,经常会将内存给占满,这是非常要注意的,常用的比如,快速排序就是用的递归调用

本篇重点介绍了GO函数(func)的声明与使用,下一篇将介绍GO的结构 struct

彻底理解Golang Map

本文目录如下,阅读本文后,将一网打尽下面Golang Map相关面试题

每个map的底层结构是hmap,hmap包含若干个结构为bmap的bucket数组.每个bucket底层都采用链表结构.此时此刻呢,我们来详细看下map的结构

bucket内存数据结构可视化如下:

注意到 key 和 value 是各自放在一起的,并不是 key/value/key/value/... 这样的形式.源码里说明这样的好处是在某些情况下可以省略掉 padding字段,节省内存空间.

map是个指针,底层指向hmap,所以是个引用类型

golang 有三个常用的高级类型 slice 、map、channel, 它们都是 引用类型 ,当引用类型作为函数参数时,可能会修改原内容数据.

golang 中没有引用传递,只有值和指针传递.所以 map 作为函数实参传递时本质上也是值传递,只不过因为 map 底层数据结构是通过指针指向实际的元素存储空间,在被调函数中修改 map,对调用者同样可见,所以 map 作为函数实参传递时表现出了引用传递的效果.

所以呢,传递 map 时,如果想修改map的内容而不是map本身,函数形参无需使用指针

map 底层数据结构是通过指针指向实际的元素 存储空间 ,这种情况下,对其中一个map的更改,会影响到其他map

map 在没有被修改的情况下,使用 range 多次遍历 map 时输出的 key 和 value 的顺序可能不同.这是 Go 语言的设计者们有意为之,在每次 range 时的顺序被随机化,旨在提示开发者们,Go 底层实现并不保证 map 遍历顺序稳定,请大家不要依赖 range 遍历结果顺序.

map 本身是无序的,且遍历时顺序还会被随机化,如果想顺序遍历 map,需要对 map key 先排序,再按照 key 的顺序遍历 map.

map默认是并发不安全的,原因如下:

Go 官方在经过了长时间的讨论后,认为 Go map 更应适配典型使用场景(不需要从多个 goroutine 中进行安全访问),而不是为了小部分情况(并发访问),导致大部分程序付出加锁代价(性能),决定了不支持.

如果想实现map线程安全,有两种方式:

方式一:使用读写锁 map ◆ sync.RWMutex

方式二:使用golang提供的 sync.Map

sync.map是用读写分离实现的,其思想是空间换时间.和map◆RWLock的实现方式相比,它做了一些优化:可以无锁访问read map,而且会优先操作read map,倘若只操作read map就可以满足要求(增删改查遍历),那就不用去操作write map(它的读写都要加锁),所以在某些特定场景中它发生锁竞争的频率会远远小于map◆RWLock的实现方式.

找到一个 B,使得 map 的装载因子在正常范围内

Go 语言中读取 map 有两种语法:带 comma 和 不带 comma.当要查询的 key 不在 map 里,带 comma 的用法会返回一个 bool 型变量提示 key 是否在 map 中;而不带 comma 的语句则会返回一个 value 类型的零值.如果 value 是 int 型就会返回 0,如果 value 是 string 类型,就会返回空字符串.

map的查找通过生成汇编码可以知道,根据 key 的不同类型,编译器会将查找函数用更具体的函数替换,以优化效率:

函数首先会检查 map 的标志位 flags.如果 flags 的写标志位此时被置 1 了,说明有其他协程在执行"写"操作,进而导致程序 panic.这也说明了 map 对协程是不安全的.

m: 桶的个数

从buckets 通过 hash m 得到对应的bucket,如果bucket正在扩容,并且没有扩容完成,则从oldbuckets得到对应的bucket

计算hash所在桶编号:

计算hash所在的槽位:

如果在 bucket 中没找到,并且 overflow 不为空,还要继续去 overflow bucket 中寻找,直到找到或是所有的 key 槽位都找遍了,包括所有的 overflow bucket.

通过上面找到了对应的槽位,这里我们再详细分析下key/value值是如何获取的:

bucket 里 key 的起始地址就是 unsafe.Pointer(b)◆dataOffset.第 i 个 key 的地址就要在此基础上跨过 i 个 key 的大小;而我们又知道,value 的地址是在所有 key 之后,所以呢第 i 个 value 的地址还需要加上所有 key 的偏移.

通过汇编语言可以看到,向 map 中插入或者修改 key,最终调用的是 mapassign 函数.

实际上插入或修改 key 的语法是一样的,只不过前者操作的 key 在 map 中不存在,而后者操作的 key 存在 map 中.

mapassign 有一个系列的函数,根据 key 类型的不同,编译器会将其优化为相应的"快速函数".

我们只用研究最一般的赋值函数 mapassign .

map的赋值会附带着map的扩容和迁移,map的扩容只是将底层数组扩大了一倍,并没有进行数据的转移,数据的转移是在扩容后逐步进行的,在迁移的过程中每进行一次赋值(access或者delete)会至少做一次迁移工作.

①判断map是否为nil

每一次进行赋值/删除操作时,只要oldbuckets != nil 则认为正在扩容,会做一次迁移工作,下面会详细说下迁移过程

根据上面查找过程,查找key所在位置,如果找到则更新,没找到则找空位插入即可

经过前面迭代寻找动作,若没有找到可插入的位置,意味着需要扩容进行插入,下面会详细说下扩容过程

通过汇编语言可以看到,向 map 中删除 key,最终调用的是 mapdelete 函数

删除的逻辑相对比较简单,大多函数在赋值操作中已经用到过,核心还是找到 key 的具体位置.寻找过程都是类似的,在 bucket 中挨个 cell 寻找.找到对应位置后,对 key 或者 value 进行"清零"操作,将 count 值减 1,将对应位置的 tophash 值置成 Empty

①.、装载因子超过阈值

在装载因子比较小的情况下,这时候 map 的查找和插入效率也很低,而第 1 点识别不出来这种情况.表面现象就是计算装载因子的分子比较小,即 map 里元素总数少,但是 bucket 数量多(真实分配的 bucket 数量多,包括大量的 overflow bucket)

上面说的 hashGrow() 函数实际上并没有真正地"搬迁",它只是分配好了新的 buckets,并将老的 buckets 挂到了 oldbuckets 字段上.真正搬迁 buckets 的动作在 growWork() 函数中,而调用 growWork() 函数的动作是在 mapassign 和 mapdelete 函数中.也就是插入或修改、删除 key 的时候,都会尝试进行搬迁 buckets 的工作.先检查 oldbuckets 是否搬迁完毕,具体来说就是检查 oldbuckets 是否为 nil.

如果未迁移完毕,赋值/删除的时候,扩容完毕后(预分配内存),不会马上就进行迁移.而是采取 增量扩容 的方式,当有访问到具体 bukcet 时,才会逐渐的进行迁移(将 oldbucket 迁移到 bucket)

在evacuate 方法实现是把这个位置对应的bucket,以及其冲突链上的数据都转移到新的buckets上.

转移的判断直接通过tophash 就可以,判断tophash中第一个hash值即可

遍历的过程,就是按顺序遍历 bucket,同时按顺序遍历 bucket 中的 key.

map遍历是无序的,如果想实现有序遍历,可以先对key进行排序

为什么遍历 map 是无序的?

如果发生过迁移,key 的位置发生了重大的变化,有些 key 飞上高枝,有些 key 则原地不动.这样,遍历 map 的结果就不可能按原来的顺序了.

如果就一个写死的 map,不会向 map 进行插入删除的操作,按理说每次遍历这样的 map 都会返回一个固定顺序的 key/value 序列吧.但是 Go 杜绝了这种做法,因为这样会给新手程序员带来误解,以为这是一定会发生的事情,在某些情况下,可能会酿成大错.

Go 做得更绝,当我们在遍历 map 时,并不是固定地从 0 号 bucket 开始遍历,每次都是从一个**随机值序号的 bucket 开始遍历,并且是从这个 bucket 的一个 随机序号的 cell **开始遍历.这样,即使你是一个写死的 map,仅仅只是遍历它,也不太可能会返回一个固定序列的 key/value 对了.

GO语言(十三):使用 Go 和 Gin 开发 RESTful API(下)

为此,您将编写以下内容:

①.、编写代码

a.添加代码以将专辑数据添加到专辑列表.

在此代码中:

①.)用于Context.BindJSON 将请求正文绑定到newAlbum.

b.更改您的main函数,使其包含该router.POST函数,如下所示.

①.)将路径中的POST方法与 /albumspostAlbums函数相关联.

a.如果服务器从上一节开始仍在运行,请停止它.

b.从包含 main.go 的目录中的命令行,运行代码.

c.从不同的命令行窗口,用于curl向正在运行的 Web 服务发出请求.

该命令应显示添加专辑的标题和 JSON.

d.与上一节一样,使用curl检索完整的专辑列表,您可以使用它来确认添加了新专辑.

该命令应显示专辑列表.

为此,您将:

a.在您在上一节中添加的函数下方postAlbums,粘贴以下代码以检索特定专辑.

此getAlbumByID函数将提取请求路径中的 ID,然后找到匹配的专辑.

(1)Context.Param用于从 URL 中检索id路径参数.当您将此处理程序映射到路径时,您将在路径中包含参数的占位符.

如上所述,实际使用中的服务可能会使用数据库查询来执行此查找.

b.最后,更改您的main,使其包含对router.GET的新调用,路径现在为/albums/:id ,如以下示例所示.

(1)将/albums/:id路径与getAlbumByID功能相关联.在 Gin 中,路径中项目前面的冒号表示该项目是路径参数.

b.在包含 main.go 的目录中的命令行中,运行代码以启动服务器.

该命令应显示您使用其 ID 的专辑的 JSON.如果找不到专辑,您将收到带有错误消息的 JSON.

恭喜!您刚刚使用 Go 和 Gin 编写了一个简单的 RESTful Web 服务.

本节包含您使用本教程构建的应用程序的代码.

GO语言(三十):访问关系型数据库(上)

本教程介绍了使用 Godatabase/sql及其标准库中的包访问关系数据库的基础知识.

您将使用的database/sql包包括用于连接数据库、执行事务、取消正在进行的操作等的类型和函数.

在本教程中,您将创建一个数据库,然后编写代码来访问该数据库.您的示例项目将是有关老式爵士乐唱片的数据存储库.

首先,为您要编写的代码创建一个文件夹.

①.、打开命令提示符并切换到您的主目录.

在 Linux 或 Mac 上:

在 Windows 上:

运行go mod init命令,为其提供新代码的模块路径.

此命令创建一个 go.mod 文件,您添加的依赖项将在其中列出以供跟踪.

注意: 在实际开发中,您会指定一个更符合您自己需求的模块路径.有关更多信息,请参阅一下文章.

GO语言(二十五):管理依赖项(上)

GO语言(二十六):管理依赖项(中)

GO语言(二十七):管理依赖项(下)

此时此刻呢,您将创建一个数据库.

在此步骤中,您将创建要使用的数据库.您将使用 DBMS 本身的 CLI 创建数据库和表,以及添加数据.

您将创建一个数据库,其中包含有关黑胶唱片上的老式爵士乐录音的数据.

这里的代码使用MySQL CLI,但大多数 DBMS 都有自己的 CLI,具有类似的功能.

①.、打开一个新的命令提示符.

在命令行,登录到您的 DBMS,如下面的 MySQL 示例所示.

将以下 SQL 代码粘贴到文件中,然后保存文件.

在此 SQL 代码中:

(1)删除名为album表. 首先执行此命令可以让您更轻松地稍后重新运行脚本.

您将使用以下形式的source命令:

此时此刻呢,您将编写一些 Go 代码进行连接,以便进行查询.

现在你已经有了一个包含一些数据的数据库,开始你的 Go 代码.

找到并导入一个数据库驱动程序,该驱动程序会将您通过database/sql包中的函数发出的请求转换为数据库可以理解的请求.

①.、在您的浏览器中,访问SQLDrivers wiki 页面以识别您可以使用的驱动程序.

(1)将您的代码添加到main包中,以便您可以独立执行它.

导入驱动程序后,您将开始编写代码以访问数据库.

现在编写一些 Go 代码,让您使用数据库句柄访问数据库.

您将使用指向结构的指针sql.DB,它表示对特定数据库的访问.

编写代码

①.、进入 main.go,在import您刚刚添加的代码下方,粘贴以下 Go 代码以创建数据库句柄.

该Config结构使代码比连接字符串更容易阅读.

为了简化代码,您调用log.Fatal结束执行并将错误打印到控制台.在生产代码中,您会希望以更优雅的方式处理错误.

文件的顶部现在应该如下所示:

①.、开始跟踪 MySQL 驱动程序模块作为依赖项.

使用go get 添加 github.com/go-sql-driver/mysql 模块作为您自己模块的依赖项.使用点参数表示"获取当前目录中代码的依赖项".

连接成功了!

此时此刻呢,您将查询一些数据.

版权声明:倡导尊重与保护知识产权。未经许可,任何人不得复制、转载、或以其他方式使用本站《原创》内容,违者将追究其法律责任。本站文章内容,部分图片来源于网络,如有侵权,请联系我们修改或者删除处理。

编辑推荐

热门文章