mysql5.7.8之后開始原生支持json. 在類似mongodb這種nosql數據庫中,json存儲數據是非常自然的, 在mysql中合理的使用json,能夠帶來極大的便利
Json字段的使用場景
在讀laravel手冊舉例子時,我們經常會看到 $user->is_admin
來判斷用戶是否為管理員,但是在用戶表中,admin往往只占很小一部分.如果單開一個is_admin字段是很沒有必要的行為.數據庫中會有大量的無意義數據存儲, 我們可以為user表創建一個 json 字段,來存儲我們的is_admin字段
[
{
id: 1,
username: 'weiwenhao',
rest: { // 冗余字段
is_admin: 1
}
},
{
id: 2,
username: 'eienao',
rest: null
}
]
復制代碼
當然即使不使用json,我們也不會使用is_admin來判斷是否為管理員.
可以通過新增admin表或者RABC來標志管理員
依舊是用戶表, 很常見的一個需求是第三方登錄,如果我們使直接在user表新增facebook_id,facebook_email,facebook_phone_number,google_id,....
字段, 可以預見這會造成大量的無意義數據(即使他們不占用內存,或者影響性能)
一種解決辦法是 使用一對多關系來解決, 既建立一個 第三方登錄表來存儲第三方登錄的id/email/phone_number等
但是我更喜歡使用json字段來解決這個問題
[
{
id: 1,
username: 'weiwenhao', rest: { is_admin: 1, facebook_id: 2348234, facebook_phone_number: 2834723234, } }, { id: 2, username: 'eienao', rest: { google_id: 2348234, google_email: xxx@gmail.com } } ] 復制代碼
可以看出,使用json字段使數據表的設計更加自然,集中,業務也相應的更加的簡單方便.
Json字段在laravel中的使用
首先是遷移文件 $table->json('rest')->nullable();
laravel對json的使用進行了一定的優化,對於更新和創建我們可以.
$user = new User; $user->{'rest->google_id'} = 'xxx'; # 如果你的rest字段為null,那么上面的操作會使 null 會變成 {google_id: "xxx"}, 不需要再做 是否為null的判定啦 # 如果僅使用上面的插入操作,也不需要在使用模型的修改器來吧 json => array, array => json啦 復制代碼
當rest字段的值為null時,批量操作無法執行, 類似
update(['rest->google_id' => 'xxx'])
這樣的操作執行無效,因此更推薦上面的方式來進行更新操作
對於查找操作可以方便的使用
User::where('rest->google_id','xxx')->firstOrFail()
關於檢索的效率問題,在后面內容中給出解決方案
Generated Column (生成列)
5.7新增了生成列, 生成列的值是根據列定義中包含的表達式計算得來.官方示例:計算直角三角形的斜邊的長度
CREATE TABLE triangle (
sidea DOUBLE,
sideb DOUBLE,
sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb)) # AS (expression) 為生成列的核心語法
);
INSERT INTO triangle (sidea, sideb) VALUES(1,1),(3,4),(6,8);
# 對於上面的插入,查詢可以得到如下結果
mysql> SELECT * FROM triangle;
+-------+-------+--------------------+
| sidea | sideb | sidec |
+-------+-------+--------------------+
| 1 | 1 | 1.4142135623730951 |
| 3 | 4 | 5 |
| 6 | 8 | 10 |
+-------+-------+--------------------+
復制代碼
上面的 sidec的值 是根據sidea和sideb計算得來, 並未實際的存儲在磁盤中.mysql5.7之前我們想要實現上面的需求可能會這樣寫sql語句
SELECT *,(SQRT(sidea * sidea + sideb * sideb)) as sidec FROM triangle;
復制代碼
上面既生成列的主要作用, 實際上生成列有兩種子類型,上面的例子屬於 virtual (虛擬) 類型的生成列, 其並沒有將sidec的值實際存儲在磁盤中.
除了virtual, 生成列還支持 stored類型,其創建語句為
#...
sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb)) STORED # stored不指定則默認為 virtual
#...
復制代碼
當行創建或者更新時, 會重新計算 sidec並將其存儲在磁盤中
生成列的另一個重要的特性是可以根據生成列表達式的計算結果建立索引. 其建立索引的方式和普通字段創建索引的方式一致.1
CREATE TABLE triangle (
sidea DOUBLE,
sideb DOUBLE,
sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb)) # AS (expression) 為生成列的核心語法
INDEX(`sidec`)
);
復制代碼
索引本身也是存儲在磁盤中的實際存在的物質, 因此 virtual 生成列 + 索引,可以達到存儲空間的最有效利用.
對於stored 生成列 + 索引, 通常不會訪問到存儲在磁盤中stored 生成列,而是直接訪問索引.因此沒有必要使用stored生成列
使用生成列為json中的字段添加索引
已user表的rest.google_id為例,建表操作
#...
`rest` json NULL,
# JSON_EXTRACT(`rest`,'$.google_id') 等價於 `rest`->'$.google_id'
# 5.7.13版本后
# JSON_UNQUOTE(JSON_EXTRACT(`rest`,'$.google_id')) 等價於 `rest`->>'$.google_id'
# 使用生成列為json添加索引時,請務必使用 JSON_UNQUOTE(JSON_EXTRACT(`rest`,'$.google_id'))/->>
`google_id` varchar GENERATED ALWAYS AS (`rest`->>'$.google_id')) NULL
UNIQUE INDEX(`google_id`)
#...
復制代碼
在laravel遷移文件中
$table->json('rest')->nullable(); $table->string('rest')->nullable()->unique()->virtualAs('`oauth`->>"$.google_id"'); 復制代碼
有了索引后,當我們執行查詢操作
select * from users where `rest`->'$.google_id' = 'xxx' # 通常使用這種更加簡單的形式
select * from users where `rest`->>'$.google_id' = 'xxx'
# 上面兩種表達式會被mysql的優化器在查詢階段自動優化為 select * from users where google_id = 'xxx'
復制代碼
一些補充
-
virtualAs(oauth->"$.google_id"');
使用 **->**符號來創建生成列會出現無法使用索引的情況, 原因不是很明了,需要繼續研究一下手冊. 另外對於創建語句GENERATED ALWAYS
的作用也不是很明了. -
關於null, 經常會看到一種言論是mysql中使用null作為字段默認值會出現無法索引的情況.但經過查詢了解,發現這是一種老中醫理論. 我更傾向於使用null作為默認值, 而不是 ''/0/0.0 ,我認為null的表達性更好, laravel中也無時無刻不在提現這種思想.
-
關於json的使用, 最近的項目中,我大部分核心表都有一個json字段,做一些非核心數據的存儲和冗余. 比
作者:weiwenhao
鏈接:https://juejin.im/post/5c1332625188251e663eb925