SQL判斷數(shù)據(jù)存不存在的正確做法(99%的人還在寫(xiě)錯(cuò)!)
還在用 COUNT(*) 判斷數(shù)據(jù)存不存在?學(xué)會(huì)這招,性能提升 10 倍!
今天咱們聊一個(gè)超實(shí)用的話題。
相信很多剛接觸數(shù)據(jù)庫(kù)的朋友,想要判斷某條數(shù)據(jù)是否存在時(shí),第一反應(yīng)就是會(huì)寫(xiě)出類(lèi)似下面的 SQL:
SELECT COUNT(*) FROM users WHERE email = 'test@example.com';
然后再在代碼里判斷,返回的數(shù)據(jù)結(jié)果是不是大于 0。
這樣寫(xiě)雖然沒(méi)有什么錯(cuò)誤,可以實(shí)現(xiàn)功能,但是,其實(shí)并不是最好的方式。
今天,就跟大家聊一聊,一個(gè)更優(yōu)雅、性能更好的方法!
先說(shuō)說(shuō) COUNT(*) 哪里不好
假設(shè)你的用戶(hù)表中有 100 萬(wàn)條數(shù)據(jù),你想看看郵箱 zhang@example.com 有沒(méi)有已經(jīng)被注冊(cè)過(guò)。
如果你使用 COUNT(*) 的話:
SELECT COUNT(*) FROM users WHERE email = 'zhang@example.com';
數(shù)據(jù)庫(kù)就會(huì)這樣工作:
- 找到第 1 條匹配的記錄:"找到了!"
- 繼續(xù)找第 2 條:"還有嗎?"
- 繼續(xù)找第 3 條:"再找找..."
- 一直找到最后:"總共找到了 1 條"
那么,現(xiàn)在問(wèn)題就來(lái)了:我們只是想知道"有沒(méi)有",但數(shù)據(jù)庫(kù)卻要告訴我們"有多少"。然而,我們壓根兒就不關(guān)心具體的數(shù)量有多少,這純粹就是妥妥的浪費(fèi)了數(shù)據(jù)庫(kù)資源,并且查詢(xún)的性能極差。
那么,我想知道數(shù)據(jù)庫(kù)中有沒(méi)有這條數(shù)據(jù)存在,又應(yīng)該如何操作呢?
正確做法:使用 EXISTS
EXISTS 就是來(lái)解決這個(gè)痛點(diǎn)的!只要有數(shù)據(jù)符合查詢(xún)條件,那么就立即返回,不會(huì)進(jìn)一步查找了。
exists 的基礎(chǔ)用法
-- ? 推薦寫(xiě)法
SELECT EXISTS (
SELECT 1 FROM users WHERE email = 'test@example.com'
) AS user_exists;這個(gè)查詢(xún)會(huì)返回:
1(或true):表示存在0(或false):表示不存在
一般情況下,只會(huì)返回
1或者0能不能返回 boolean 值,取決于你使用的 orm 的封裝。
為什么寫(xiě)SELECT 1
有的童鞋看到了上面的 SQL,就比較好奇了:為什么是 SELECT 1 而不是 SELECT * 呢?
其實(shí)在這個(gè)場(chǎng)景中,下面的這些寫(xiě)法,效果都是一樣的,但 SELECT 1 最簡(jiǎn)潔。
SELECT EXISTS (SELECT 1 FROM users WHERE email = 'test@example.com'); SELECT EXISTS (SELECT * FROM users WHERE email = 'test@example.com'); SELECT EXISTS (SELECT email FROM users WHERE email = 'test@example.com');
因?yàn)?EXISTS 只關(guān)心"有沒(méi)有結(jié)果",不關(guān)心"具體是什么結(jié)果"。所以寫(xiě) SELECT 1 也可以在代碼層面上看起來(lái)簡(jiǎn)潔又高效。
實(shí)際應(yīng)用
場(chǎng)景一:用戶(hù)注冊(cè)時(shí)檢查郵箱
-- 檢查郵箱是否已被注冊(cè)
SELECT EXISTS (
SELECT 1 FROM users
WHERE email = 'newuser@example.com'
) AS email_taken;
-- 返回 1 表示已被占用,0 表示可以使用場(chǎng)景二:查詢(xún)有訂單的用戶(hù)
-- 找出所有有過(guò)訂單的用戶(hù)
SELECT u.id, u.name, u.email
FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);這個(gè)查詢(xún)的意思是:對(duì)于每個(gè)用戶(hù),檢查訂單表里是否存在該用戶(hù)的訂單記錄。
場(chǎng)景三:查詢(xún)沒(méi)有訂單的用戶(hù)
-- 找出從來(lái)沒(méi)下過(guò)單的用戶(hù)
SELECT u.id, u.name, u.email
FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);NOT EXISTS 就是"不存在"的意思。
性能對(duì)比
我們用一個(gè)真實(shí)例子來(lái)看看性能差異:
-- 假設(shè)用戶(hù)表中有 50 萬(wàn)條記錄
-- 使用 COUNT(*) 的方式
SELECT COUNT(*) FROM users WHERE city = '上海';
-- 執(zhí)行時(shí)間:150ms(需要統(tǒng)計(jì)所有上海的用戶(hù))
-- 使用 EXISTS 方式
SELECT EXISTS (
SELECT 1 FROM users WHERE city = '上海'
) AS has_sh_users;
-- 執(zhí)行時(shí)間:3ms(找到第一個(gè)就直接停止了)性能直接提升了 50 倍! 不過(guò)具體的執(zhí)行時(shí)間,也取決于硬件設(shè)備的情況。
為什么這么快?因?yàn)?EXISTS 找到第一條符合條件的記錄就立刻返回 true,不會(huì)繼續(xù)往下找了。
在 Go 中怎么用?
假設(shè)我們用 Go + MySQL 開(kāi)發(fā)一個(gè)用戶(hù)系統(tǒng):
基礎(chǔ)用法
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
// 檢查郵箱是否已存在
func CheckEmailExists(db *sql.DB, email string) (bool, error) {
var exists bool
query := `
SELECT EXISTS (
SELECT 1 FROM users
WHERE email = ?
)`
err := db.QueryRow(query, email).Scan(&exists)
if err != nil {
return false, err
}
return exists, nil
}
func main() {
// 連接數(shù)據(jù)庫(kù)
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/mydb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 檢查郵箱是否存在
email := "test@example.com"
exists, err := CheckEmailExists(db, email)
if err != nil {
log.Fatal(err)
}
if exists {
fmt.Printf("郵箱 %s 已被注冊(cè)\n", email)
} else {
fmt.Printf("郵箱 %s 可以使用\n", email)
}
}實(shí)際業(yè)務(wù)場(chǎng)景
// 用戶(hù)注冊(cè)邏輯
func RegisterUser(db *sql.DB, email, password string) error {
// 1. 先檢查郵箱是否已存在
exists, err := CheckEmailExists(db, email)
if err != nil {
return fmt.Errorf("檢查郵箱失敗: %v", err)
}
if exists {
return fmt.Errorf("郵箱 %s 已被注冊(cè)", email)
}
// 2. 郵箱可用,執(zhí)行注冊(cè)邏輯
_, err = db.Exec(`
INSERT INTO users (email, password, created_at)
VALUES (?, ?, NOW())
`, email, password)
if err != nil {
return fmt.Errorf("注冊(cè)失敗: %v", err)
}
fmt.Printf("用戶(hù) %s 注冊(cè)成功!\n", email)
return nil
}
// 檢查用戶(hù)是否有訂單
func UserHasOrders(db *sql.DB, userID int) (bool, error) {
var hasOrders bool
query := `
SELECT EXISTS (
SELECT 1 FROM orders
WHERE user_id = ? AND status != 'cancelled'
)`
err := db.QueryRow(query, userID).Scan(&hasOrders)
return hasOrders, err
}
// 獲取用戶(hù)信息,同時(shí)檢查是否為 VIP
func GetUserWithVIPStatus(db *sql.DB, userID int) error {
type UserInfo struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
IsVIP bool `json:"is_vip"`
}
var user UserInfo
query := `
SELECT
u.id,
u.name,
u.email,
EXISTS (
SELECT 1 FROM memberships m
WHERE m.user_id = u.id
AND m.status = 'active'
AND m.expired_at > NOW()
) AS is_vip
FROM users u
WHERE u.id = ?`
err := db.QueryRow(query, userID).Scan(
&user.ID, &user.Name, &user.Email, &user.IsVIP,
)
if err != nil {
return err
}
fmt.Printf("用戶(hù)信息: %+v\n", user)
return nil
}幾個(gè)建議點(diǎn)
1. 記得建索引
-- 為了讓 EXISTS 查詢(xún)更快,記得在經(jīng)常查詢(xún)的字段上建索引 CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_orders_user_id ON orders(user_id);
2. 處理 NULL 值
-- 如果字段可能為 NULL,記得特殊處理
SELECT EXISTS (
SELECT 1 FROM users
WHERE phone IS NOT NULL
AND phone = '13800138000'
) AS phone_exists;3. 不要在 EXISTS 里寫(xiě) ORDER BY
-- 沒(méi)必要排序
SELECT EXISTS (
SELECT 1 FROM users
WHERE city = '上海'
ORDER BY created_at -- 這個(gè)排序完全沒(méi)啥卵用
);
-- 直接開(kāi)擼
SELECT EXISTS (
SELECT 1 FROM users
WHERE city = '上海'
);最后
現(xiàn)在,當(dāng)你想要去查詢(xún)數(shù)據(jù)是否存在的時(shí)候,知道應(yīng)該用哪個(gè)了吧?
- 數(shù)據(jù)有沒(méi)有,存不存在,直接用
exists - 要是必須知道數(shù)量有多少,那么才用
count
到此這篇關(guān)于SQL判斷數(shù)據(jù)存不存在正確做法的文章就介紹到這了,更多相關(guān)SQL判斷數(shù)據(jù)存不存在內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ubuntu linux下使用Qt連接MySQL數(shù)據(jù)庫(kù)的方法
Linux下完整的MySQL開(kāi)發(fā)需要安裝服務(wù)器端,如果安裝客戶(hù)端也沒(méi)什么不好。直接在軟件中心搜mysql,把client和server選上。2011-08-08
MySQL數(shù)據(jù)庫(kù)索引和事務(wù)圖文詳解
在當(dāng)今數(shù)據(jù)驅(qū)動(dòng)的時(shí)代,數(shù)據(jù)庫(kù)的高效與可靠性是業(yè)務(wù)系統(tǒng)的核心支柱,而索引和事務(wù)作為數(shù)據(jù)庫(kù)的兩大基石,直接影響著數(shù)據(jù)查詢(xún)性能與操作安全性,這篇文章主要介紹了MySQL數(shù)據(jù)庫(kù)索引和事務(wù)的相關(guān)資料,需要的朋友可以參考下2026-01-01
一篇文章掌握MySQL的索引查詢(xún)優(yōu)化技巧
這篇文章主要給大家介紹了關(guān)于如何通過(guò)一篇文章掌握MySQL的索引查詢(xún)優(yōu)化技巧,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用MySQL具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
使用SQL語(yǔ)句統(tǒng)計(jì)數(shù)據(jù)時(shí)sum和count函數(shù)中使用if判斷條件的講解
今天小編就為大家分享一篇關(guān)于使用SQL語(yǔ)句統(tǒng)計(jì)數(shù)據(jù)時(shí)sum和count函數(shù)中使用if判斷條件的講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02
Mysql恢復(fù)誤刪庫(kù)表數(shù)據(jù)完整場(chǎng)景演示
在開(kāi)發(fā)和在生產(chǎn)中總會(huì)出現(xiàn)各種各樣的失誤和意味,當(dāng)MySQL的數(shù)據(jù)或表被刪除后不要慌,下面這篇文章主要給大家介紹了關(guān)于Mysql恢復(fù)誤刪庫(kù)表數(shù)據(jù)的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07
MySQL如何對(duì)數(shù)據(jù)進(jìn)行排序圖文詳解
我們知道從MySQL表中使用SQL SELECT語(yǔ)句來(lái)讀取數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于MySQL如何對(duì)數(shù)據(jù)進(jìn)行排序的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08

