足球资料库数据/孙祥/nba五佳球/足球直播哪个平台好 - cctv5今日现场直播

更新多行數(shù)據(jù),然后把更新的結(jié)果讀出來,這樣的 SQL 要怎么寫?
2016-03-04 15:25:11   來源: mengyidan1988   評(píng)論:0 點(diǎn)擊:

考慮這樣一種場(chǎng)景,或許還挺常見的:我們需要在關(guān)系數(shù)據(jù)庫中更新一行或多行數(shù)據(jù)的多個(gè)字段,更新完了還不算,還得拿到被更新的某一個(gè)字段的結(jié)果。 再考慮這樣一種場(chǎng)景:我們需要在關(guān)系數(shù)據(jù)庫中更新一行或多行數(shù)據(jù)的多個(gè)字段,更新完了還不算,還得拿到這批被更新的記錄的主鍵,以便操作其他的有關(guān)聯(lián)的表。 這么說也許太抽象,就拿點(diǎn)贊計(jì)數(shù)來打個(gè)比方(做為
考慮這樣一種場(chǎng)景,或許還挺常見的:我們需要在關(guān)系數(shù)據(jù)庫中更新一行或多行數(shù)據(jù)的多個(gè)字段,更新完了還不算,還得拿到被更新的某一個(gè)字段的結(jié)果。

再考慮這樣一種場(chǎng)景:我們需要在關(guān)系數(shù)據(jù)庫中更新一行或多行數(shù)據(jù)的多個(gè)字段,更新完了還不算,還得拿到這批被更新的記錄的主鍵,以便操作其他的有關(guān)聯(lián)的表。

這么說也許太抽象,就拿點(diǎn)贊計(jì)數(shù)來打個(gè)比方(做為點(diǎn)贊狂魔的我,前不久才在朋友的博文下面強(qiáng)行點(diǎn)了 666 個(gè)贊)。假設(shè)有下面一張表,就叫 likes 好了,記錄了一個(gè)網(wǎng)站里面每個(gè)能被點(diǎn)贊的對(duì)象被贊的次數(shù)。id 是一個(gè)無業(yè)務(wù)含義的自增主鍵,gmt_xxx 分別是無業(yè)務(wù)含義的記錄創(chuàng)建時(shí)間和記錄更新時(shí)間。object_id 是能被點(diǎn)贊的對(duì)象的主鍵,相當(dāng)于外鍵的作用,只不過由應(yīng)用邏輯去保證關(guān)聯(lián)表的數(shù)據(jù)一致性,數(shù)據(jù)庫不感知;count 字段記錄的是這個(gè)對(duì)象被贊的次數(shù)。
+--------------+------------+------+-----+---------+----------------+
| Field        | Type       | Null | Key | Default | Extra          |
+--------------+------------+------+-----+---------+----------------+
| id           | bigint(20) | NO   | PRI | NULL    | auto_increment |
| gmt_created  | datetime   | NO   |     | NULL    |                |
| gmt_modified | datetime   | NO   |     | NULL    |                |
| object_id    | bigint(20) | NO   |     | NULL    |                |
| count        | bigint(20) | NO   |     | 0       |                |
+--------------+------------+------+-----+---------+----------------+
于是我們?cè)陧撁嫔宵c(diǎn)了贊,前端頁面向后端服務(wù) POST 一個(gè)請(qǐng)求, 后端服務(wù)要記錄這次點(diǎn)贊行為。于是前端和后端工程師在點(diǎn)贊 API 的返回值上開始了討論:是后端簡(jiǎn)單返回一個(gè) OK 表示成功處理,前端收到 OK 后在頁面上自行把點(diǎn)贊數(shù) +1 呢,還是后端除了返回 OK 表示成功,還要返回當(dāng)時(shí)這個(gè)對(duì)象的被贊次數(shù),然后前端在頁面上更新被贊次數(shù)?

做為后端工程師當(dāng)然想實(shí)現(xiàn)為前者,多簡(jiǎn)單啊,一個(gè) update 語句更新一下 gmt_modified 和 count 然后返回 OK 就搞定了,要不然還得多查一下。但是前端工程師不樂意了,如果能讓后端接口多返回點(diǎn)數(shù)據(jù)給前端多好,這么沒頭沒腦的 +1 就把業(yè)務(wù)邏輯摻進(jìn)來了,說好的后端負(fù)責(zé)數(shù)據(jù)前端負(fù)責(zé)展現(xiàn)呢。

前后端撕逼大戰(zhàn)引起了產(chǎn)品經(jīng)理的注意。產(chǎn)品經(jīng)理說,返回當(dāng)時(shí)的被贊次數(shù)能讓用戶感受到其他用戶的熱情,就這么定了,為了用戶體驗(yàn)!

呵呵,用戶體驗(yàn)這個(gè)尚方寶劍真好用呢~

于是呢,后端工程師回去寫出了這樣的 SQL:
update likes set gmt_modified = now(), count = count + 1 where object_id = ?;select count from likes where object_id = ?;

于是這樣子就能寫出基本滿足功能的點(diǎn)贊 API 了!

注意到這兩條 SQL 語句不在一個(gè)事務(wù)中,因此 select 語句拿到的 count 并不一定是它前面那條 update 更新的結(jié)果,可能被別的 update 更新了,所以用戶不僅僅感受到了從點(diǎn)下鼠標(biāo)開始,到數(shù)據(jù)庫開始執(zhí)行 update 這段時(shí)間內(nèi)其他用戶的熱情,還感受到了從 update 執(zhí)行后,到 select 開始執(zhí)行這段時(shí)間內(nèi)其他用戶的熱情。

發(fā)現(xiàn)這個(gè)問題之后,后端工程師就開始想啊,要是那個(gè)用戶體驗(yàn)至上的產(chǎn)品經(jīng)理覺得這個(gè)感受熱情的時(shí)間窗口太長了,用戶體驗(yàn)不好,想把后面那段從 update 到 select 的時(shí)間窗口拿掉,咋整?這個(gè)時(shí)間窗口要是有別的請(qǐng)求過來,數(shù)據(jù)肯定就污染了,得把別的請(qǐng)求擋在外面,沒 select 完之前都別 update。

怎么把別的請(qǐng)求擋掉呢?加事務(wù)唄,而且事務(wù)隔離級(jí)別必須在 Read committed 及以上。事務(wù)一開始就用 update 給那一行用行鎖給鎖定了,別的請(qǐng)求只能等到 select 返回事務(wù)結(jié)束才能去 update 同一行。

哎喲不錯(cuò)哦,這個(gè)辦法好。

可是,為了這種小需求,這么隨意就開一個(gè)事務(wù),Code Review 的時(shí)候會(huì)被架構(gòu)師駁回的吧?本來這種完全走索引的查詢,服務(wù)器和數(shù)據(jù)庫之間網(wǎng)絡(luò)通信的時(shí)間開銷就是數(shù)據(jù)庫內(nèi)查詢的時(shí)間開銷的好多倍,再來個(gè) begin 和 commit 這兩活寶,簡(jiǎn)直就是生生把查詢耗時(shí)翻倍的節(jié)奏。本來就只是在多表更新這種需要保證原子性的地方不得已開個(gè)事務(wù),這種小需求也開事務(wù)的話,總有點(diǎn)殺雞用牛刀的感覺。

誒,網(wǎng)絡(luò)通信的確是個(gè)麻煩的事情,有沒有辦法把這個(gè)事務(wù)里面的網(wǎng)絡(luò)通信開銷減少一點(diǎn)呢?如果事務(wù)里面耗時(shí)減少,占用連接的時(shí)間就相應(yīng)減少,系統(tǒng)就能夠承載更多的并發(fā)請(qǐng)求!

存儲(chǔ)過程?如果把 begin,update,select,commit 四個(gè)語句寫到一個(gè)存儲(chǔ)過程里面,網(wǎng)絡(luò)通信次數(shù)就從四次減少到一次了,性能提升 75%!想歸想做歸做,之前總聽架構(gòu)師說,我們的系統(tǒng)是互聯(lián)網(wǎng)架構(gòu),如果沒有特別的理由,業(yè)務(wù)邏輯都得放在應(yīng)用服務(wù)器中,數(shù)據(jù)庫只做存儲(chǔ)不做業(yè)務(wù)。這種小需求應(yīng)該拿不出什么特別的理由去用存儲(chǔ)過程吧。

有沒有其他辦法,既不用開啟事務(wù),又能夠準(zhǔn)確拿到 update 的結(jié)果呢?去 StackOverflow 看看吧~

還真發(fā)現(xiàn)有人問了類似的 問題,7 年之前。雖然沒法用一個(gè)查詢搞定,但是還是有辦法在不開事務(wù)的條件下實(shí)現(xiàn)的!借助一個(gè)變量,把更新的結(jié)果放到變量里,然后再在同一個(gè) session 中把變量值讀出來。的確是一種巧妙的做法。
update likes set gmt_modified = now(), count = @cnt := count + 1 where object_id = ?;select @cnt as count;

因?yàn)?Web 應(yīng)用在和數(shù)據(jù)庫交互的時(shí)候都會(huì)使用連接池,執(zhí)行 SQL 前獲取一個(gè)連接,然后在連接里巴拉巴拉執(zhí)行一堆 SQL,然后再把連接還給連接池,所以我們基本上不用擔(dān)心 update 和 select 不在一個(gè) session 的情況,一般來說只要代碼保證 update 和 select 在同一個(gè)連接上執(zhí)行就好了。

真的沒有辦法用一個(gè)查詢搞定嗎?

在這個(gè)問題被提出來的時(shí)候,在 MySQL 里面,還真是沒有辦法用一個(gè)查詢搞定。甚至直到現(xiàn)在,在 Oracle 維護(hù)的官方版本的 MySQL 里頭,還是沒法用一個(gè)查詢搞定。

但是這個(gè)世界上,MySQL 也有很多分支啊,除了徹底分裂出去的 MariaDB,還有 Percona 這個(gè)號(hào)稱完全兼容 MySQL 的增強(qiáng)版。除了提供源碼的 Percona,還有 Alibaba 維護(hù)的AliSQL,還有數(shù)據(jù)庫即服務(wù)的阿里云 RDS

如果你正在使用阿里云 RDS,可以嘗試這樣一種寫法,把 update 和 select 合并為一條 SQL,進(jìn)一步減少網(wǎng)絡(luò)開銷和數(shù)據(jù)庫開銷,提升性能。
select count from update likes set gmt_modified = now(), count = count + 1 where object_id = ?;

關(guān)于這個(gè)增強(qiáng)的用法,其原理、性能對(duì)比等等,詳見其作者的博文 Oracle/PostgreSQL UPDATE…RETURNING…在MySQL中的實(shí)現(xiàn),本文不贅述。

需要注意的是,這個(gè)增強(qiáng)語法并沒有在云數(shù)據(jù)庫文檔中明確給出,將其應(yīng)用于生產(chǎn)環(huán)境前,最好先咨詢相關(guān)專家。

做為例子,這里嘗試列舉幾個(gè)適合使用 select from update 這種增強(qiáng)語法的場(chǎng)景。

第一個(gè)例子,分布式唯一主鍵生成器。

在面臨較大的訪問流量時(shí),我們一般會(huì)將數(shù)據(jù)庫水平拆分,成為數(shù)據(jù)庫集群,數(shù)據(jù)根據(jù)分表字段散列到不同的數(shù)據(jù)庫主從節(jié)點(diǎn)上。在單庫單表的數(shù)據(jù)庫中,我們的表的主鍵通常用的是一個(gè)自增的數(shù)字,但是水平拆分之后就不能這么用了,為了保證不同分表的數(shù)據(jù)依然滿足主鍵唯一的約束,我們需要一個(gè)分布式的主鍵生成器。

且不論這個(gè)生成器如何實(shí)現(xiàn),考慮到主鍵是 insert 操作必不可少的字段,主鍵生成器必須高性能高可用,一種策略就是批量獲取主鍵并緩存在內(nèi)存中,這樣子可以成百上千倍地減少對(duì)主鍵生成器的請(qǐng)求。
select max_avaliable from update primary_keys set max_avaliable = max_avaliable + ? where primary_key = ?;

第一個(gè)參數(shù)傳批量獲取的數(shù)量 N,第二個(gè)參數(shù)傳主鍵標(biāo)識(shí),這樣子從讀取到的最大可用主鍵開始,往前推 N 個(gè)都是可用的不重復(fù)的主鍵。

第二個(gè)例子,點(diǎn)贊。

第三個(gè)例子,電子商務(wù)系統(tǒng)中的庫存扣減,和點(diǎn)贊正好是反向操作,點(diǎn)贊是加,庫存是減。

基本上 select from update 適用于那些需要從更新的記錄中讀取一些字段的場(chǎng)景,特別是能夠根據(jù)索引定位到少數(shù)的幾條記錄的時(shí)候,性能表現(xiàn)良好。

如果你覺得這個(gè)增強(qiáng)能夠幫助改善系統(tǒng)性能,不妨試試~反正我用過之后,就開始嫌棄不支持這個(gè)功能的 原生 MySQL 了!

本文來自:blog.jamespan.me

相關(guān)熱詞搜索:mysql database 數(shù)據(jù)庫

上一篇:在首席架構(gòu)師眼里,架構(gòu)的本質(zhì)是……
下一篇:ITeye新聞熱點(diǎn)月刊:2016年2月總第96期發(fā)布了!

分享到: 收藏