Find、Take、First和Last函数的区别
创始人
2025-07-04 06:32:04
0

大家好,我是渔夫子。

在gorm中,要想从数据库中查找数据有多种方法,可以通过Find、Take和First来查找。但它们之间又有一些不同。本文就详细介绍下他们之间的不同。

一、准备工作

首先我们有一个m_tests表,其中id字段是自增的主键,同时该表里有3条数据。如下:

CREATE TABLE `m_tests` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL DEFAULT '' COMMENT '姓名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;

INSERT INTO test01.m_test (id,name) VALUES (1,'John'), (2,'Jack'),(3,'David');

基于这个表,我们来看看这几个函数查询出来的结果是什么。

二、First函数

我们通过ToSql函数将First函数转成对应的sql语句来看。如下:

func main() {
	dsn := "username:password@tcp(127.0.0.1:3306)/test01?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"

	config := &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 禁用表名复数
		}}
	db, _ := gorm.Open(mysql.Open(dsn), config)

	var row MTest

	sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.First(&row)
	})

	fmt.Printf("接收的sql语句:%s\n", sql)
}

通过该程序,可以看到最终的sql语句如下:

接收的sql语句:SELECT * FROM `m_test` ORDER BY `m_test`.`id` LIMIT 1

发现First函数是通过主键排序后,只获取一条数据。我们在通过explain来解释一下该条语句:

explain SELECT * FROM `m_test` ORDER BY `m_test`.`id` LIMIT 1

其输出结果如下:

也就是说在查询的时候也只扫描一行数据。也就是说First函数只扫描一行数据。

三、Last函数

同样,我们还是通过ToSQL来讲Last函数转化的sql语句打印出来:

func main() {
	dsn := "username:password@tcp(127.0.0.1:3306)/test01?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"

	config := &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 禁用表名复数
		}}
	db, _ := gorm.Open(mysql.Open(dsn), config)

	var rows []MTest

	sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.Last(&rows)
	})

	fmt.Printf("接收的sql语句:%s\n", sql)

  db.Last(&rows)
  fmt.Printf("最终接收:%+v\n", rows)
}

我们看到Last转换成的sql语句如下:

接收的sql语句:SELECT * FROM `m_test` ORDER BY `m_test`.`id` DESC LIMIT 1

所以,Take实际上是按主键倒序排列,并且只获取1行数据的一个sql。

我们再看最终获取的结果rows,虽然是个数组,但也只有一行数据。:

最终结果数据:[{Id:6 Name:}]

所以,Last和First的相同点在于只扫描到表的一条目标数据后就截止了,并赋值给接收变量。不同点在于First是按主键正序排列,Last是按主键倒序排列。

四、Take函数

再来看看Take函数的执行过程。如下:

func main() {
	dsn := "username:password@tcp(127.0.0.1:3306)/test01?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"

	config := &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 禁用表名复数
		}}
	db, _ := gorm.Open(mysql.Open(dsn), config)

	var row MTest

	sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.Take(&row)
	})

	fmt.Printf("接收的sql语句:%s\n", sql)
}

Take函数执行时最终转换成的sql语句如下:

SELECT * FROM `m_test` LIMIT 1

也是只获取一行数据,但和First不同的是缺少了Order BY m_test.id``。

我们再通过explain来解释下该条语句,如下, type列是ALL,rows列是3,因为我们表里只有3行数据。是全表扫描,然后再随机获取一行数据。如下:

mysql> explain SELECT * FROM `m_test` LIMIT 1;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | m_test | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | NULL  |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.09 sec)

所以,Take函数是扫描全表,并随机获取一条数据。所以,Take函数要比First函数性能差。

同时,我们注意到,因为在sql语句中可以看到都有LIMIT 1的限制,所以Take和First都只能获取一条数据,即便是给传递了一个数组,也只能获取一行数据,不能获取多行数据。

五、Find函数

再来看看Take函数的执行过程。我们首先给Find函数传递一个普通的非切片变量,如下:

func main() {
	dsn := "username:password@tcp(127.0.0.1:3306)/test01?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"

	config := &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 禁用表名复数
		}}
	db, _ := gorm.Open(mysql.Open(dsn), config)

	var row MTest

	sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.Find(&row)
	})

	fmt.Printf("接收的sql语句:%s\n", sql)
}

转换成的sql语句如下:

接收的sql语句:SELECT * FROM `m_test`

和First和Take相比,缺少了Order子句和Limit子句。扫描的是整个表,获取的也是表的所有数据,但因为接收者是一个非切片变量,所以最终只接收了一行数据到row中。

我们再来看看给Find传递一个切片变量来接收的情况:

func main() {
	dsn := "username:password@tcp(127.0.0.1:3306)/test01?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"

	config := &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 禁用表名复数
		}}
	db, _ := gorm.Open(mysql.Open(dsn), config)

	var rows []MTest

	tx.Find(&rows)

	fmt.Printf("rows:%+v\n", rows)
}

这个结果是接收所有查找到的行的数据到rows中。所以大家一定要注意,在使用Find查询的时候一定要加Where条件和查询的数量,以避免扫描和查询全表的数据,尤其是在大数量的表中。

六、总结

本文主要讲解了First、Last、Take和Find查询函数的不同之处。希望在使用过程中大家根据自己的应用场景选择合适的函数。

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
30分钟搞定iOS自定义相机 最近公司的项目中用到了相机,由于不用系统的相机,UI给的相机切图,必须自定义才可以。就花时间简单研究...
Intel将Moblin社区控... 本周二,非营利机构Linux基金会宣布,他们将担负起Moblin社区的管理工作,而这之前,Mobli...