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