面試經(jīng)典問(wèn)題,如何提高數(shù)據(jù)庫(kù)的性能?
簡(jiǎn)介
一個(gè)有趣的面試問(wèn)題,我已經(jīng)聽(tīng)到并問(wèn)過(guò)很多次了。
"你將如何提高數(shù)據(jù)庫(kù)的性能?"
我喜歡這個(gè)問(wèn)題,因?yàn)?,就像我討論過(guò),它從更廣泛的角度來(lái)衡量候選人的技能。云計(jì)算架構(gòu)師會(huì)考慮數(shù)據(jù)庫(kù)的架構(gòu),考慮讀取復(fù)制和分片,后端或全棧工程師可能會(huì)考慮應(yīng)用層面的變化,如優(yōu)化的SQL查詢(xún)、連接池,而數(shù)據(jù)庫(kù)人員可能會(huì)考慮數(shù)據(jù)庫(kù)的配置、插件等。
這個(gè)問(wèn)題可能有很多答案,因?yàn)槲蚁肷钊肓私饷總€(gè)答案,所以我將分別寫(xiě)三篇文章,每篇都針對(duì)某一類(lèi)答案。
第一篇將更多地討論應(yīng)用層面和SQL的變化。這些可能是我對(duì)直接編寫(xiě)代碼的開(kāi)發(fā)人員的期望(例如,編寫(xiě)與數(shù)據(jù)庫(kù)服務(wù)器互動(dòng)的NodeJS、Python應(yīng)用程序的開(kāi)發(fā)人員)。
第二種是要更注重架構(gòu)層面的變化,管理服務(wù)等。他們會(huì)更關(guān)注云計(jì)算架構(gòu)師或?qū)ο到y(tǒng)設(shè)計(jì)概念有良好了解的人。
第三組答案將更注重于數(shù)據(jù)庫(kù)和操作系統(tǒng)的配置。
請(qǐng)記住,這是一個(gè)非常廣泛的話題,這是我對(duì)如何回答這個(gè)問(wèn)題的看法,我將提供進(jìn)一步閱讀的鏈接,并盡可能多地提供實(shí)際的例子。
我也在使用軟件工程的stackexchange數(shù)據(jù)集作為我的例子,你可以找到在使用Pandas將數(shù)據(jù)加載到Postgres之前,我還對(duì)數(shù)據(jù)進(jìn)行了一些轉(zhuǎn)換和調(diào)整,如果你有興趣了解更多,請(qǐng)告訴我,我可以分享jupyter-notebook。這些數(shù)據(jù)相當(dāng)容易理解,我的大多數(shù)例子應(yīng)該僅限于Posts表,它簡(jiǎn)單地定義了StackOverflow上發(fā)布的問(wèn)題,屬性包括標(biāo)題、正文、創(chuàng)建日期等。
問(wèn)題
問(wèn)題是,"我的數(shù)據(jù)庫(kù)越來(lái)越慢,你會(huì)如何提高它的性能?". 在這篇文章中,我假設(shè)是一個(gè)SQL數(shù)據(jù)庫(kù),特別是Postgres。
把這個(gè)問(wèn)題看成是一個(gè)兩部分的問(wèn)題,盡管它沒(méi)有明確這樣說(shuō)。第一部分是 "為什么",第二部分是如何解決。為了理解 "為什么",你需要對(duì)問(wèn)題進(jìn)行調(diào)試,一旦你知道了數(shù)據(jù)庫(kù)性能緩慢的原因,你就可以提出一個(gè)可能的解決方案。
為了便于閱讀,我從可能的答案開(kāi)始,涵蓋與每個(gè)答案相關(guān)的權(quán)衡,然后我將解決你需要提出的反面問(wèn)題,以調(diào)試問(wèn)題的原因。
在你向下滾動(dòng)之前,想一想你會(huì)如何回答這個(gè)問(wèn)題,如果你發(fā)現(xiàn)我的文章中沒(méi)有包括的內(nèi)容,請(qǐng)?jiān)谠u(píng)論中告訴我。
可能的答案
請(qǐng)記住,每一個(gè)答案都是有取舍的。
索引
如果你的SELECT查詢(xún)變得非常慢,因?yàn)槟阍跈z查某個(gè)條件,索引可以提供一種方法來(lái)改善你的數(shù)據(jù)庫(kù)讀取性能。
你可以在一組特定的列上創(chuàng)建一個(gè)索引,數(shù)據(jù)庫(kù)將創(chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)保存數(shù)據(jù)庫(kù)的這些列,這樣你就可以得到這些列的快速查詢(xún)。
例如,如果我想獲取用戶(hù)user8創(chuàng)建的帖子,我可以運(yùn)行以下查詢(xún)
SELECT * FROM posts WHERE owner_display_name='user8';
然而,這迫使數(shù)據(jù)庫(kù)掃描表中的所有行,以找到owner_display_name為 "user8 "的帖子。這種類(lèi)型的掃描被稱(chēng)為順序掃描,因?yàn)閿?shù)據(jù)庫(kù)正在掃描整個(gè)表。正如名字所暗示的,這種類(lèi)型的查找不是最優(yōu)化的解決方案。
運(yùn)行上面的查詢(xún),大約需要150ms的時(shí)間來(lái)執(zhí)行。這個(gè)查詢(xún)的查詢(xún)計(jì)劃(數(shù)據(jù)庫(kù)對(duì)如何為你的查詢(xún)讀取表所做的規(guī)劃)也表明,它將對(duì)這個(gè)查詢(xún)進(jìn)行順序掃描
要得到同樣的結(jié)果,一個(gè)更好的方法是在列owner_display_name上創(chuàng)建一個(gè)索引。這將創(chuàng)建一個(gè)單獨(dú)的數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)庫(kù)可以使用這個(gè)新的數(shù)據(jù)結(jié)構(gòu)快速查詢(xún)?cè)摿械奶囟ㄖ怠?/p>
創(chuàng)建一個(gè)索引很簡(jiǎn)單。
CREATE INDEX posts_owner_display_name_idx ON posts (owner_display_name)。
一旦我們創(chuàng)建了索引,我們就不需要做任何其他事情來(lái)使用它。數(shù)據(jù)庫(kù)足夠聰明,知道什么時(shí)候使用索引,什么時(shí)候不使用。運(yùn)行同樣的查詢(xún)。
SELECT * FROM posts WHERE owner_display_name='user8';
我們現(xiàn)在得到了完全不同的結(jié)果?,F(xiàn)在查詢(xún)?cè)?ms左右就完成了!這比沒(méi)有索引時(shí)快了50倍。這比沒(méi)有索引時(shí)快了50倍!
查詢(xún)計(jì)劃還指出,數(shù)據(jù)庫(kù)現(xiàn)在正在使用我們所創(chuàng)建的索引
雖然沒(méi)有什么是免費(fèi)的,索引也有一定的成本。
由于我們現(xiàn)在正在創(chuàng)建一個(gè)不同的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)你表中的相同數(shù)據(jù),所以你的表的每一行都需要更多的存儲(chǔ)空間。除此之外,現(xiàn)在數(shù)據(jù)庫(kù)必須確保每當(dāng)你向表中添加新的行時(shí),索引都是最新的,所以在編寫(xiě)新的行時(shí),它也為數(shù)據(jù)庫(kù)增加了更多的工作。
索引可以是修復(fù)數(shù)據(jù)庫(kù)讀取性能的一個(gè)好方法,但可能會(huì)減慢寫(xiě)入性能。
索引本身就是一個(gè)復(fù)雜的話題,網(wǎng)上有很多資源可以更深入地討論其性能影響。 如果你想更多地了解與索引相關(guān)的權(quán)衡,那么 "索引 "是一個(gè)很好的起點(diǎn)。 如果你想了解更多關(guān)于索引的信息,視頻也更詳細(xì)地解釋了索引。
創(chuàng)建物化視圖
你可以在數(shù)據(jù)庫(kù)中創(chuàng)建兩種類(lèi)型的視圖,簡(jiǎn)單視圖和物化視圖。
簡(jiǎn)單視圖作為查詢(xún)的別名,而物化視圖則存儲(chǔ)查詢(xún)的結(jié)果。這意味著,你可以預(yù)先計(jì)算查詢(xún),將結(jié)果存儲(chǔ)在物化視圖中,并在用戶(hù)實(shí)際要求時(shí)快速向用戶(hù)顯示結(jié)果,而不是在用戶(hù)請(qǐng)求時(shí)運(yùn)行查詢(xún)。
讓我們用一個(gè)例子來(lái)討論這個(gè)問(wèn)題。假設(shè)我想根據(jù)帖子的瀏覽量來(lái)了解帖子的類(lèi)別 --
做到這一點(diǎn)的一個(gè)方法是運(yùn)行以下查詢(xún)
SELECT
CASE
WHEN view_count < 10 THEN 1
WHEN view_count < 100 THEN 2
WHEN view_count < 1000 THEN 3
WHEN view_count < 10000 THEN 4
WHEN view_count < 100000 THEN 5
ELSE 6
END as view_count_category_index,
CASE
WHEN view_count < 10 THEN '0-10'
WHEN view_count < 100 THEN '10-100'
WHEN view_count < 1000 THEN '100-1000'
WHEN view_count < 10000 THEN '1000-10000'
WHEN view_count < 100000 THEN '10000-100000'
ELSE '100000+'
END as view_count_category,
COUNT(*)
FROM
posts
GROUP BY view_count_category, view_count_category_index
ORDER BY view_count_category_index ASC;
執(zhí)行時(shí)間: 257.556 ms
250毫秒比我想要的要高一點(diǎn)。我希望它能低得多,比如說(shuō)<50ms。創(chuàng)建一個(gè)視圖非常簡(jiǎn)單,我只需要運(yùn)行CREATE VIEW [VIEWNAME] AS [QUERY]。讓我們創(chuàng)建一個(gè)簡(jiǎn)單的(非化的)視圖。
CREATE VIEW non_mat_view_count_view AS
SELECT
CASE
WHEN view_count < 10 THEN 1
WHEN view_count < 100 THEN 2
WHEN view_count < 1000 THEN 3
WHEN view_count < 10000 THEN 4
WHEN view_count < 100000 THEN 5
ELSE 6
END as view_count_category_index,
CASE
WHEN view_count < 10 THEN '0-10'
WHEN view_count < 100 THEN '10-100'
WHEN view_count < 1000 THEN '100-1000'
WHEN view_count < 10000 THEN '1000-10000'
WHEN view_count < 100000 THEN '10000-100000'
ELSE '100000+'
END as view_count_category,
COUNT(*)
FROM
posts
GROUP BY view_count_category, view_count_category_index
ORDER BY view_count_category_index ASC;
然后嘗試從視圖中獲取結(jié)果。
SELECT * FROM non_mat_view_count_view;
它確實(shí)給了我正確的輸出。但是當(dāng)用EXPLAIN ANALYZE運(yùn)行時(shí),我得到的執(zhí)行時(shí)間仍然非常相似,為250-260ms。發(fā)生這種情況的原因是,簡(jiǎn)單的視圖只是存儲(chǔ)了查詢(xún),當(dāng)我們?cè)噲D使用該視圖時(shí),會(huì)重新執(zhí)行該查詢(xún)。
然而,創(chuàng)建一個(gè)物化視圖是不同的。在這里,當(dāng)我們創(chuàng)建物化視圖時(shí),它實(shí)際上會(huì)存儲(chǔ)查詢(xún)的結(jié)果,當(dāng)用戶(hù)請(qǐng)求這些數(shù)據(jù)時(shí),它不需要計(jì)算結(jié)果,由于它只需要獲取結(jié)果,所以它可以比執(zhí)行查詢(xún)或簡(jiǎn)單視圖快很多。
創(chuàng)建物化視圖與創(chuàng)建簡(jiǎn)單視圖非常相似,只是我們使用CREATE MATERIALIZED VIEW而不是CREATE VIEW。
CREATE MATERIALIZED VIEW mat_view_count_view AS
SELECT
CASE
WHEN view_count < 10 THEN 1
WHEN view_count < 100 THEN 2
WHEN view_count < 1000 THEN 3
WHEN view_count < 10000 THEN 4
WHEN view_count < 100000 THEN 5
ELSE 6
END as view_count_category_index,
CASE
WHEN view_count < 10 THEN '0-10'
WHEN view_count < 100 THEN '10-100'
WHEN view_count < 1000 THEN '100-1000'
WHEN view_count < 10000 THEN '1000-10000'
WHEN view_count < 100000 THEN '10000-100000'
ELSE '100000+'
END as view_count_category,
COUNT(*)
FROM
posts
GROUP BY view_count_category, view_count_category_index
ORDER BY view_count_category_index ASC;
當(dāng)我們運(yùn)行EXPLAIN ANALYZE SELECT * FROM mat_view_count_view;我們得到的執(zhí)行時(shí)間是0.027ms。這比執(zhí)行查詢(xún)或使用我們以前的簡(jiǎn)單視圖要快得多!事實(shí)上,這是12000倍的速度!
然而,這也是有代價(jià)的。由于結(jié)果是預(yù)先計(jì)算的,對(duì)表的任何更新都不會(huì)自動(dòng)更新結(jié)果。你需要手動(dòng)運(yùn)行REFRESH MATERIALIZED VIEW mat_view_count_view來(lái)更新視圖。如果你想實(shí)現(xiàn)自動(dòng)化,有很多方法可以做到這一點(diǎn),正如在《MATERIALIZED VIEW》中解釋的那樣。 但所有這些方法都有一些缺點(diǎn)。缺點(diǎn)包括數(shù)據(jù)不一致(例如,用戶(hù)獲取過(guò)時(shí)的視圖計(jì)數(shù)類(lèi)別數(shù)據(jù)),以及數(shù)據(jù)庫(kù)服務(wù)器的性能問(wèn)題,因?yàn)楦乱晥D意味著再次計(jì)算這個(gè)查詢(xún),等等。
因此,雖然物化視圖是提高讀取性能的好方法,但在向數(shù)據(jù)庫(kù)寫(xiě)入時(shí)可能會(huì)導(dǎo)致性能問(wèn)題或一致性問(wèn)題。當(dāng)數(shù)據(jù)的更新頻率較低,并且可以容忍輕微的數(shù)據(jù)不一致或不準(zhǔn)確時(shí),這種解決方案可能是有意義的。
應(yīng)用層面上的連接池
在我談?wù)撨B接池之前,讓我解釋一下什么是連接。
要連接到一個(gè)數(shù)據(jù)庫(kù),你的應(yīng)用程序需要建立一個(gè)數(shù)據(jù)庫(kù)連接。把這個(gè)連接看成是一個(gè)假想的管道,請(qǐng)求和響應(yīng)將通過(guò)這個(gè)管道流動(dòng)。
你需要數(shù)據(jù)庫(kù)主機(jī)名、數(shù)據(jù)庫(kù)名、憑證等來(lái)建立一個(gè)新的連接,這個(gè)連接確實(shí)有一些數(shù)據(jù)與之相關(guān)。我認(rèn)為維基百科上關(guān)于連接的文章把它說(shuō)得非常好。
"數(shù)據(jù)庫(kù)連接是有限的和昂貴的,相對(duì)于在其上進(jìn)行的操作來(lái)說(shuō),創(chuàng)建的時(shí)間可能長(zhǎng)得不成比例。當(dāng)一個(gè)應(yīng)用程序需要更新數(shù)據(jù)庫(kù)時(shí),創(chuàng)建、使用和關(guān)閉數(shù)據(jù)庫(kù)連接的效率很低。"
一旦你有一個(gè)連接,你就可以開(kāi)始向數(shù)據(jù)庫(kù)發(fā)送請(qǐng)求。大多數(shù)數(shù)據(jù)庫(kù)只允許你在每個(gè)連接上一次執(zhí)行一個(gè)操作。這意味著,如果一個(gè)事務(wù)的執(zhí)行需要100毫秒,那么每個(gè)連接只能實(shí)現(xiàn)每秒10個(gè)事務(wù)(或10TPS)。因此,使用單一連接限制了你可以運(yùn)行的事務(wù)數(shù)量。
因此,讓我們回顧一下。我們知道打開(kāi)和關(guān)閉連接是昂貴的,我們也知道我們不能使用單一的連接,因?yàn)槟菚?huì)使我們的系統(tǒng)陷入瓶頸。
那么,解決方案是什么呢?
好吧,我們可以維護(hù)一些連接并重復(fù)使用它們。這就是所謂的連接池。想象一下一個(gè)連接池,如果這能讓人更容易記住的話。
幸運(yùn)的是,大多數(shù)客戶(hù)端庫(kù)都具備相當(dāng)好的連接池功能,我們可以在代碼中快速編寫(xiě)。
事實(shí)上,它是如此廣泛,以至于pg庫(kù)文檔中的例子,這是一個(gè)非常流行的用于postgres的nodejs庫(kù),它同時(shí)具有連接到數(shù)據(jù)庫(kù)的連接池方法和直接客戶(hù)端方法
const { Pool, Client } = require('pg')
// pools will use environment variables
// for connection information
const pool = new Pool()
pool.query('SELECT NOW()', (err, res) => {
console.log(err, res)
pool.end()
})
// you can also use async/await
const res = await pool.query('SELECT NOW()')
await pool.end()
// clients will also use environment variables
// for connection information
const client = new Client()
await client.connect()
const res = await client.query('SELECT NOW()')
await client.end()
還有一些重要的配置,我們可以在數(shù)據(jù)庫(kù)服務(wù)器上做連接池,這可能會(huì)影響性能,但由于我想在另一篇文章中介紹數(shù)據(jù)庫(kù)服務(wù)器和操作系統(tǒng)的配置,我現(xiàn)在先不談這個(gè)。
說(shuō)到權(quán)衡,我不認(rèn)為連接池有什么大的權(quán)衡,至少我沒(méi)有遇到過(guò),也沒(méi)有讀到過(guò)。如果你碰巧知道,請(qǐng)留言幫助我,也幫助其他會(huì)讀這篇文章的人。
最后。 這是一個(gè)了不起的起點(diǎn),如果你想了解更多關(guān)于連接池的信息。
應(yīng)用層面的緩存
對(duì)于很多應(yīng)用來(lái)說(shuō),大多數(shù)的讀取只針對(duì)少量的數(shù)據(jù)??紤]一下Twitter的情況。大多數(shù)被瀏覽的推文可能是重要和受歡迎的人,如政治家、名人等。類(lèi)似的趨勢(shì)也可能存在于許多流行的閱讀量大的網(wǎng)站上。
事實(shí)上,讓我們看看到目前為止我們一直在使用的數(shù)據(jù)。請(qǐng)記住,這是來(lái)自softwareengineering.stackexchange.com的真實(shí)流量數(shù)據(jù)。
為了分析這些數(shù)據(jù),我按瀏覽量對(duì)數(shù)據(jù)進(jìn)行排序,然后按十分位數(shù)進(jìn)行分組。簡(jiǎn)而言之,下圖顯示了哪一個(gè)十分位數(shù)獲得了多少百分比的總瀏覽量。
數(shù)據(jù)顯示,前10%(第一個(gè)十分位數(shù))的帖子占了約95%的瀏覽量,接下來(lái)的10%(第二個(gè)十分位數(shù))占了約4.8%的瀏覽量。
SELECT
(SUM(view_count) * 100.0) / (SELECT SUM(view_count) FROM actual_posts) as percentage,
SUM(view_count),
decile
FROM (
SELECT
post_id,
view_count,
ntile(10) over (order by view_count DESC) as decile
FROM actual_posts
) sum_data
GROUP BY decile
ORDER BY decile
當(dāng)按百分位數(shù)而不是十位數(shù)計(jì)算時(shí),瀏覽量的差異更加明顯,前1%的帖子占了50%以上的瀏覽量。
這意味著,通過(guò)找到一種方法來(lái)服務(wù)前1%的帖子,你可以加快你得到的50%的請(qǐng)求,或者找到一種方法來(lái)服務(wù)前10%的帖子,你可以加快你得到的95%的請(qǐng)求!由于這為其他請(qǐng)求釋放了你的服務(wù)器,其他請(qǐng)求也可以使用更多的資源,因此速度會(huì)更快!"。
因此,讓我們來(lái)看看問(wèn)題的陳述,我們需要找到一種方法來(lái)存儲(chǔ)相對(duì)較少的數(shù)據(jù),但能夠非常快速地獲取它。另一方面,我們?nèi)匀恍枰鎯?chǔ)其余的數(shù)據(jù),但我們不需要超快的檢索。
實(shí)現(xiàn)這一目標(biāo)的一個(gè)好方法是將經(jīng)常被獲取的少量數(shù)據(jù)存儲(chǔ)在RAM中,而將大量數(shù)據(jù)存儲(chǔ)在SSD中。這樣,你就可以非常迅速地滿(mǎn)足大部分的請(qǐng)求,并在需要的時(shí)候偶爾打到SSD上。這就是所謂的高速緩存。
有很多方法可以實(shí)現(xiàn)這一點(diǎn),但由于我在這篇文章中討論的是應(yīng)用層面的變化,所以一個(gè)簡(jiǎn)單的實(shí)現(xiàn)方法是在你的應(yīng)用中添加一個(gè)簡(jiǎn)單的哈希圖。請(qǐng)注意,這絕對(duì)不是最好的方法,還有其他更好的緩存方案,但我將在后面介紹更復(fù)雜的方案。
這個(gè)想法很簡(jiǎn)單,為你經(jīng)常收到的請(qǐng)求在哈希姆中建立一個(gè)小的緩沖區(qū)。當(dāng)用戶(hù)請(qǐng)求一個(gè)帖子時(shí),檢查它是否存在于你的緩存中,如果存在,那么就把帖子發(fā)給用戶(hù),如果不存在,那么你就可以沖擊你的數(shù)據(jù)庫(kù),把數(shù)據(jù)存儲(chǔ)在SSD中。對(duì)我們來(lái)說(shuō),幸運(yùn)的是,已經(jīng)有很多庫(kù)實(shí)現(xiàn)了這一點(diǎn)。
不過(guò)這也有很大的缺點(diǎn)。
一個(gè)大的問(wèn)題是與數(shù)據(jù)的一致性有關(guān)。如果你在RAM上存儲(chǔ)了少量的數(shù)據(jù),你需要確保在數(shù)據(jù)庫(kù)中的數(shù)據(jù)被更新時(shí)更新這些數(shù)據(jù),這樣用戶(hù)就不會(huì)收到過(guò)時(shí)的數(shù)據(jù)。這就打開(kāi)了一個(gè)難題,因?yàn)楝F(xiàn)在你需要決定你更新數(shù)據(jù)的頻率,是每分鐘、每秒鐘、每一次交易等等。
這個(gè)解決方案可能會(huì)出現(xiàn)的另一個(gè)問(wèn)題(盡管我們?cè)谖磥?lái)的文章中可能會(huì)討論的其他緩存解決方案不會(huì)出現(xiàn)這個(gè)問(wèn)題)是使你的服務(wù)器更加復(fù)雜和有狀態(tài)。終止你的服務(wù)器將意味著失去這些存儲(chǔ)在RAM中的數(shù)據(jù),而啟動(dòng)新的服務(wù)器將意味著從數(shù)據(jù)庫(kù)中快速獲取大量的數(shù)據(jù)(這被稱(chēng)為 thundering herd problem).當(dāng)一個(gè)新的服務(wù)器突然出現(xiàn)時(shí),這種大量的取數(shù)會(huì)使數(shù)據(jù)庫(kù)在短時(shí)間內(nèi)變慢。
別擔(dān)心,這些問(wèn)題的解決方案是存在的,在很多情況下,緩存是一個(gè)非常好的通用解決方案,特別是當(dāng)數(shù)據(jù)一致性不是問(wèn)題的時(shí)候。
編寫(xiě)優(yōu)化的SQL查詢(xún)
在應(yīng)用層面,提高性能的一個(gè)好方法是編寫(xiě)優(yōu)化的SQL查詢(xún)。即使性能不是一個(gè)問(wèn)題,為了未來(lái)的可擴(kuò)展性,編寫(xiě)優(yōu)化的SQL查詢(xún)?nèi)匀皇歉玫淖龇ā?/p>
有相當(dāng)多的方法可以讓你寫(xiě)出優(yōu)化的SQL查詢(xún),例如,試圖避免OFFSET和找到更好的方法來(lái)實(shí)現(xiàn)分頁(yè),或者避免SELECT * ,等等。網(wǎng)上有很多很好的資源,有助于幫助你理解優(yōu)化的SQL查詢(xún)。
另一個(gè)好的組織技術(shù)可以是編寫(xiě)非物化視圖,并在應(yīng)用層面的SQL中使用它們來(lái)代替長(zhǎng)的SQL語(yǔ)句。
對(duì)于那些不知道什么是非物化視圖的人來(lái)說(shuō),可以把它看作是一種查詢(xún)的別名。例如,我們假設(shè)我有一個(gè)名為post的表。這個(gè)表有以下的模式---
在大多數(shù)情況下,我們將考慮到以下欄目
比方說(shuō),我想寫(xiě)一個(gè)SQL查詢(xún),用于根據(jù)帖子的瀏覽量進(jìn)行分類(lèi),例如,如果我想要這樣的輸出
這方面的查詢(xún)是。
SELECT
CASE
WHEN view_count < 10 THEN 1
WHEN view_count < 100 THEN 2
WHEN view_count < 1000 THEN 3
WHEN view_count < 10000 THEN 4
WHEN view_count < 100000 THEN 5
ELSE 6
END as view_count_category_index,
CASE
WHEN view_count < 10 THEN '0-10'
WHEN view_count < 100 THEN '10-100'
WHEN view_count < 1000 THEN '100-1000'
WHEN view_count < 10000 THEN '1000-10000'
WHEN view_count < 100000 THEN '10000-100000'
ELSE '100000+'
END as view_count_category,
COUNT(*)
FROM
posts
GROUP BY view_count_category, view_count_category_index
ORDER BY view_count_category_index ASC;
這有點(diǎn)復(fù)雜。我可以創(chuàng)建一個(gè)視圖,而不是在我的代碼中寫(xiě)這個(gè)。??
CREATE VIEW view_count_category_view AS
SELECT
CASE
WHEN view_count < 10 THEN 1
WHEN view_count < 100 THEN 2
WHEN view_count < 1000 THEN 3
WHEN view_count < 10000 THEN 4
WHEN view_count < 100000 THEN 5
ELSE 6
END as view_count_category_index,
CASE
WHEN view_count < 10 THEN '0-10'
WHEN view_count < 100 THEN '10-100'
WHEN view_count < 1000 THEN '100-1000'
WHEN view_count < 10000 THEN '1000-10000'
WHEN view_count < 100000 THEN '10000-100000'
ELSE '100000+'
END as view_count_category,
COUNT(*)
FROM
posts
GROUP BY view_count_category, view_count_category_index
ORDER BY view_count_category_index ASC;
那么我就可以不在代碼中寫(xiě)這個(gè)SQL邏輯,而是簡(jiǎn)單地使用
SELECT * FROM view_count_category_view
當(dāng)我在上面的視圖上執(zhí)行查詢(xún)時(shí),在幕后,數(shù)據(jù)庫(kù)實(shí)際上會(huì)執(zhí)行創(chuàng)建視圖的實(shí)際查詢(xún)。
?這使得我的代碼更加簡(jiǎn)潔,而且在將來(lái),我可以更新視圖邏輯,而不需要在應(yīng)用層面上進(jìn)行修改和重新部署。還有一種方法可以實(shí)現(xiàn)類(lèi)似的功能,叫做存儲(chǔ)過(guò)程
在回答問(wèn)題前先反問(wèn)
在回答這個(gè)問(wèn)題之前,你一般應(yīng)該問(wèn)幾個(gè)反面的問(wèn)題,以幫助更好地理解這個(gè)問(wèn)題。這些可以幫助你衡量系統(tǒng)中的瓶頸問(wèn)題。整個(gè)系統(tǒng)可能相當(dāng)復(fù)雜,可能有很多原因?qū)е聰?shù)據(jù)庫(kù)開(kāi)始表現(xiàn)不佳。為了更好地了解原因,并更好地了解系統(tǒng)的要求,你可以向面試官提出一些問(wèn)題,這些問(wèn)題可以幫助你找出最佳解決方案。
由于這一部分需要對(duì)上面的答案有一定的了解,所以我在討論了可能的答案后將其列入,但你在回答之前可能應(yīng)該提出反問(wèn)。
是讀取性能慢還是寫(xiě)入性能慢?
一個(gè)非常重要的因素可以推動(dòng)你的決策,就是有關(guān)數(shù)據(jù)庫(kù)的讀寫(xiě)性能如何。許多改善一個(gè)的解決方案可能也會(huì)以消極的方式影響另一個(gè)。例如,創(chuàng)建物化視圖會(huì)改善你的讀取性能,但會(huì)在數(shù)據(jù)庫(kù)服務(wù)器上增加額外的負(fù)載,可能會(huì)影響寫(xiě)入性能。
我們使用的是哪個(gè)數(shù)據(jù)庫(kù)??
另一個(gè)重要因素可能是我們正在使用的數(shù)據(jù)庫(kù)。每個(gè)數(shù)據(jù)庫(kù)都是為一個(gè)特定的使用情況而建立的。使用錯(cuò)誤的數(shù)據(jù)庫(kù)類(lèi)型會(huì)嚴(yán)重影響你的性能。
?例如,當(dāng)你想進(jìn)行分析性查詢(xún)時(shí),使用Postgres這樣的關(guān)系型數(shù)據(jù)庫(kù)通常不是一個(gè)好的做法。雖然關(guān)系型數(shù)據(jù)庫(kù)可以執(zhí)行大量的分析功能,但它們?cè)谠擃I(lǐng)域的功能和性能比專(zhuān)門(mén)為這些類(lèi)型的操作建立的數(shù)據(jù)庫(kù)(例如,Cassandra或Redshift)要有限得多。
很多數(shù)據(jù)庫(kù)都是為特定的問(wèn)題而存在的,一般來(lái)說(shuō),它們?cè)谔幚韱?wèn)題陳述時(shí)的表現(xiàn)會(huì)好很多。有一些數(shù)據(jù)庫(kù)用于搜索(例如ElasticSearch),用于地理空間數(shù)據(jù)(例如Neo4J),用于時(shí)間序列數(shù)據(jù)(例如Prometheus),用于存儲(chǔ)臨時(shí)數(shù)據(jù)(例如Redis或Memcached),等等。
了解用戶(hù)如何使用你的服務(wù)
另一個(gè)需要了解的重要因素是用戶(hù)如何使用你的服務(wù)。用戶(hù)是全天都在發(fā)送請(qǐng)求,還是有特定的高峰時(shí)間??
對(duì)不準(zhǔn)確或過(guò)時(shí)的數(shù)據(jù)的容忍度是多少(例如,用戶(hù)一般不會(huì)介意一個(gè)帖子的喜歡數(shù)過(guò)時(shí)了幾分鐘)?
他們主要是在進(jìn)行讀取查詢(xún),還是大部分在進(jìn)行寫(xiě)入查詢(xún)?
他們所存儲(chǔ)的數(shù)據(jù)有多敏感?你需要考慮的任何監(jiān)管要求?
?這些問(wèn)題可以幫助你了解你應(yīng)該關(guān)注什么,是讀取性能還是寫(xiě)入性能,以及你在ACID屬性方面有多大的靈活性。例如,如果用戶(hù)可以接受過(guò)時(shí)的數(shù)據(jù),那么你可以考慮使用刷新頻率相對(duì)較低的物化視圖。
時(shí)間表
另一個(gè)要始終牢記的重要因素是需要多快的解決方案。它是一個(gè)緊急問(wèn)題,數(shù)據(jù)庫(kù)完全無(wú)法使用?或者是一個(gè)輕微的性能下降,公司希望確保系統(tǒng)的可擴(kuò)展性。
總結(jié)
這是我對(duì)如何回答這個(gè)問(wèn)題的看法。我還會(huì)談?wù)摳嚓P(guān)于架構(gòu)方面的事情,也會(huì)談?wù)撘稽c(diǎn)關(guān)于配置方面的事情,但我會(huì)在另一篇文章中包括這些。