Find、Take、First和Last函數(shù)的區(qū)別
大家好,我是漁夫子。
在gorm中,要想從數(shù)據(jù)庫(kù)中查找數(shù)據(jù)有多種方法,可以通過(guò)Find、Take和First來(lái)查找。但它們之間又有一些不同。本文就詳細(xì)介紹下他們之間的不同。
一、準(zhǔn)備工作
首先我們有一個(gè)m_tests表,其中id字段是自增的主鍵,同時(shí)該表里有3條數(shù)據(jù)。如下:
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');
基于這個(gè)表,我們來(lái)看看這幾個(gè)函數(shù)查詢出來(lái)的結(jié)果是什么。
二、First函數(shù)
我們通過(guò)ToSql函數(shù)將First函數(shù)轉(zhuǎn)成對(duì)應(yīng)的sql語(yǔ)句來(lái)看。如下:
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, // 禁用表名復(fù)數(shù)
}}
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語(yǔ)句:%s\n", sql)
}
通過(guò)該程序,可以看到最終的sql語(yǔ)句如下:
接收的sql語(yǔ)句:SELECT * FROM `m_test` ORDER BY `m_test`.`id` LIMIT 1
發(fā)現(xiàn)First函數(shù)是通過(guò)主鍵排序后,只獲取一條數(shù)據(jù)。我們?cè)谕ㄟ^(guò)explain來(lái)解釋一下該條語(yǔ)句:
explain SELECT * FROM `m_test` ORDER BY `m_test`.`id` LIMIT 1
其輸出結(jié)果如下:
也就是說(shuō)在查詢的時(shí)候也只掃描一行數(shù)據(jù)。也就是說(shuō)First函數(shù)只掃描一行數(shù)據(jù)。
三、Last函數(shù)
同樣,我們還是通過(guò)ToSQL來(lái)講Last函數(shù)轉(zhuǎn)化的sql語(yǔ)句打印出來(lái):
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, // 禁用表名復(fù)數(shù)
}}
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語(yǔ)句:%s\n", sql)
db.Last(&rows)
fmt.Printf("最終接收:%+v\n", rows)
}
我們看到Last轉(zhuǎn)換成的sql語(yǔ)句如下:
接收的sql語(yǔ)句:SELECT * FROM `m_test` ORDER BY `m_test`.`id` DESC LIMIT 1
所以,Take實(shí)際上是按主鍵倒序排列,并且只獲取1行數(shù)據(jù)的一個(gè)sql。
我們?cè)倏醋罱K獲取的結(jié)果rows,雖然是個(gè)數(shù)組,但也只有一行數(shù)據(jù)。:
最終結(jié)果數(shù)據(jù):[{Id:6 Name:}]
所以,Last和First的相同點(diǎn)在于只掃描到表的一條目標(biāo)數(shù)據(jù)后就截止了,并賦值給接收變量。不同點(diǎn)在于First是按主鍵正序排列,Last是按主鍵倒序排列。
四、Take函數(shù)
再來(lái)看看Take函數(shù)的執(zhí)行過(guò)程。如下:
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, // 禁用表名復(fù)數(shù)
}}
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語(yǔ)句:%s\n", sql)
}
Take函數(shù)執(zhí)行時(shí)最終轉(zhuǎn)換成的sql語(yǔ)句如下:
SELECT * FROM `m_test` LIMIT 1
也是只獲取一行數(shù)據(jù),但和First不同的是缺少了Order BY m_test.id``。
我們?cè)偻ㄟ^(guò)explain來(lái)解釋下該條語(yǔ)句,如下, type列是ALL,rows列是3,因?yàn)槲覀儽砝镏挥?行數(shù)據(jù)。是全表掃描,然后再隨機(jī)獲取一行數(shù)據(jù)。如下:
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函數(shù)是掃描全表,并隨機(jī)獲取一條數(shù)據(jù)。所以,Take函數(shù)要比First函數(shù)性能差。
同時(shí),我們注意到,因?yàn)樵趕ql語(yǔ)句中可以看到都有LIMIT 1的限制,所以Take和First都只能獲取一條數(shù)據(jù),即便是給傳遞了一個(gè)數(shù)組,也只能獲取一行數(shù)據(jù),不能獲取多行數(shù)據(jù)。
五、Find函數(shù)
再來(lái)看看Take函數(shù)的執(zhí)行過(guò)程。我們首先給Find函數(shù)傳遞一個(gè)普通的非切片變量,如下:
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, // 禁用表名復(fù)數(shù)
}}
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語(yǔ)句:%s\n", sql)
}
轉(zhuǎn)換成的sql語(yǔ)句如下:
接收的sql語(yǔ)句:SELECT * FROM `m_test`
和First和Take相比,缺少了Order子句和Limit子句。掃描的是整個(gè)表,獲取的也是表的所有數(shù)據(jù),但因?yàn)榻邮照呤且粋€(gè)非切片變量,所以最終只接收了一行數(shù)據(jù)到row中。
我們?cè)賮?lái)看看給Find傳遞一個(gè)切片變量來(lái)接收的情況:
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, // 禁用表名復(fù)數(shù)
}}
db, _ := gorm.Open(mysql.Open(dsn), config)
var rows []MTest
tx.Find(&rows)
fmt.Printf("rows:%+v\n", rows)
}
這個(gè)結(jié)果是接收所有查找到的行的數(shù)據(jù)到rows中。所以大家一定要注意,在使用Find查詢的時(shí)候一定要加Where條件和查詢的數(shù)量,以避免掃描和查詢?nèi)淼臄?shù)據(jù),尤其是在大數(shù)量的表中。
六、總結(jié)
本文主要講解了First、Last、Take和Find查詢函數(shù)的不同之處。希望在使用過(guò)程中大家根據(jù)自己的應(yīng)用場(chǎng)景選擇合適的函數(shù)。