mysql5.7.8以後開始原生支持json. 在相似mongodb這種nosql數據庫中,json存儲數據是很是天然的, 在mysql中合理的使用json,可以帶來極大的便利php
在讀laravel手冊舉例子時,咱們常常會看到 $user->is_admin
來判斷用戶是否爲管理員,可是在用戶表中,admin每每只佔很小一部分.若是單開一個is_admin字段是很沒有必要的行爲.數據庫中會有大量的無心義數據存儲, 咱們能夠爲user表建立一個 json 字段,來存儲咱們的is_admin字段mysql
[
{
id: 1,
username: 'weiwenhao',
rest: { // 冗餘字段
is_admin: 1
}
},
{
id: 2,
username: 'eienao',
rest: null
}
]
複製代碼
固然即便不使用json,咱們也不會使用is_admin來判斷是否爲管理員.laravel
能夠經過新增admin表或者RABC來標誌管理員sql
依舊是用戶表, 很常見的一個需求是第三方登陸,若是咱們使直接在user表新增facebook_id,facebook_email,facebook_phone_number,google_id,....
字段, 能夠預見這會形成大量的無心義數據(即便他們不佔用內存,或者影響性能)mongodb
一種解決辦法是 使用一對多關係來解決, 既創建一個 第三方登陸表來存儲第三方登陸的id/email/phone_number等數據庫
可是我更喜歡使用json字段來解決這個問題express
[
{
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
首先是遷移文件 $table->json('rest')->nullable();
bash
laravel對json的使用進行了必定的優化,對於更新和建立咱們能夠.nosql
$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()
關於檢索的效率問題,在後面內容中給出解決方案
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生成列
已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字段,作一些非核心數據的存儲和冗餘. 比