明明表中沒這條數據,竟然還能查出來?
1.還原問題現場
有一天下午,有用戶反饋說,它自定義的品牌:yoyo,一直都添加不成功。
我查了一下服務器的日志,并沒有異常。
在我們的創建商品頁面,用戶可以選擇已有品牌,也可以自己自定義新的品牌。
前端做了一個品牌的下來列表,為了方便用戶查找,支持搜索。
用戶可以輸入關鍵字搜索品牌。
如果下拉框中出現了,則可以選擇使用。
如果下拉框中沒有數據,則在輸入框中標識這個品牌是用戶自定義的品牌。
然后通過創建商品接口,將該品牌添加到數據庫當中。
現在的問題是yoyo這個品牌,用戶自定義了,但不能保存到數據庫當中。
這就非常奇怪了。
2 分析問題
為了查明這個問題,我先查詢了數據庫中的品牌表:
select * from brand where `name`='yoyo';
確實沒有查出yoyo這個品牌。
但意外查出YOYO這個品牌。
圖片
它是yoyo英文字母的大寫。
奇怪,我們查小寫的yoyo字符串,為什么會把大寫的YOYO查出來了?
于是,我查了brand表的表結構。
CREATE TABLE `brand` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(30) NOT NULL COMMENT '品牌名稱',
`create_user_id` bigint NOT NULL COMMENT '創建人ID',
`create_user_name` varchar(30) NOT NULL COMMENT '創建人名稱',
`create_time` datetime(3) DEFAULT NULL COMMENT '創建日期',
`update_user_id` bigint DEFAULT NULL COMMENT '修改人ID',
`update_user_name` varchar(30) DEFAULT NULL COMMENT '修改人名稱',
`update_time` datetime(3) DEFAULT NULL COMMENT '修改時間',
`is_del` tinyint(1) DEFAULT '0' COMMENT '是否刪除 1:已刪除 0:未刪除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='品牌表';
品牌表使用的存儲引擎ENGINE是InnoDB,為了保證表的事務性。
字符集CHARSET用的utf8mb4,可以保存一些表情符號等特殊字符。
校對規則COLLATE用的utf8_unicode_ci。
字符集是一組符號和編碼的集合,而校對規則是用于比較字符集中字符的規則。
例如,utf8mb4字符集支持存儲Unicode字符,而utf8mb4_0900_ai_ci校對規則定義了如何比較這些字符。
在MySQL中使用show collation指令,可以查看到所有COLLATE。
以utf8mb4為例,該編碼所支持的所有COLLATE如下圖所示。
圖片
主要包含了三種:
- 以_ci結尾的。
- 以_bin結尾的。
- 以_cs結尾的。
ci是case insensitive的縮寫,意思是大小寫不敏感,即忽略大小寫。
cs是case sensitive的縮寫,意思是大小寫敏感,即區分大小寫。
還有一種是bin,它是將字符串中的每一個字符用二進制數據存儲,區分大小寫。
使用最多的是 utf8mb4_general_ci(默認的)和 utf8mb4_bin。
我們的brand表,使用的COLLATE是utf8mb4_general_ci,它不區分大小寫。
難怪下面的這條sql:
select * from brand where `name`='yoyo';
數據庫中明明沒有小寫的yoyo這條數據,但卻能把大寫的YOYO數據查出來。
3.如何解決問題?
知道原因了,就好辦了。
第一個想到的是把brand表的COLLATE改成utf8mb4_bin不就搞定了?
這樣確實可以非常快速解決問題。
但我仔細想了一下。
品牌這種基礎數據,yoyo和YOYO正常情況下應該是同一個品牌,應該只有一個id,不區分大小寫才是正確的做法。
如果brand表的COLLATE改成了utf8mb4_bin,區分大小寫,不就會出現兩個不同的id,這樣品牌表不就會產生重復的數據,后面會導致商品也可能會重復。
如果后面商品也重復了,就會帶來非常多的問題。
因此,我們要在brand表做好控制,不應該區分大小寫,保證品牌不會重復。
既然修改brand表的COLLATE這個方案不行,那么,只能修改業務邏輯了。
目前有兩種解決方案:
- 前端搜索品牌時,不區分大小寫。
- 前端品牌下拉控件,改成分頁的,搜索品牌的功能,改成調用后端接口實現。
方案1適合品牌數據量少的情況。
方案2適合品牌數據量多的情況。
我們的品牌數據,其實在不斷增加,因此,決定使用方案2。
后端提供一個分頁查詢品牌的接口,并且支持不區分大小寫的模糊搜索功能。
但這樣還不能100%保證,品牌數據在brand表中不會重復。
還需要給name字段增加唯一索引。
這樣改造之后,后面用戶輸入yoyo,但數據庫中有YOYO,在品牌下拉列表中會顯示YOYO,用戶可以直接選擇使用。
這樣對用戶的交互更友好一些。
這是一類問題,可以衍生一下。
有些屬性值表也有類似的問題。
比如用戶自定義屬性值之后,如果業務邏輯中有通過屬性id查詢屬性值集合,再拿這個屬性值集合跟自定義屬性值做判斷的時候,就需要忽略大小寫做判斷了。
其實,在我們的實際工作中,這樣的場景很多,趕緊排查一下代碼,看看你有沒有這個問題?