設(shè)計(jì)優(yōu)化)
1. 從一次數(shù)據(jù)合并的“翻車(chē)”說(shuō)起前幾天團(tuán)隊(duì)里一個(gè)剛?cè)胄械臄?shù)據(jù)分析師小張跑來(lái)問(wèn)我說(shuō)他寫(xiě)了個(gè)SQL查詢本來(lái)只想關(guān)聯(lián)兩個(gè)小表結(jié)果跑出來(lái)的數(shù)據(jù)量爆炸了從幾百條直接變成了幾萬(wàn)條系統(tǒng)差點(diǎn)卡死。我一看他的代碼典型的SELECT * FROM table_a, table_b沒(méi)有加任何關(guān)聯(lián)條件。我告訴他“兄弟你這是不小心搞出了笛卡爾積啊?!彼荒樸隆暗芽柗e是啥聽(tīng)起來(lái)像數(shù)學(xué)課上的東西?!逼鋵?shí)不只是SQL只要你處理數(shù)據(jù)、做系統(tǒng)設(shè)計(jì)、甚至寫(xiě)業(yè)務(wù)邏輯這個(gè)“笛卡爾積”都像房間里的大象你稍不注意它就會(huì)跳出來(lái)給你制造一堆垃圾數(shù)據(jù)消耗大量資源。簡(jiǎn)單來(lái)說(shuō)笛卡爾積就是把兩個(gè)集合里的每一個(gè)元素都毫無(wú)保留地、一對(duì)一地配對(duì)一遍。聽(tīng)起來(lái)好像沒(méi)什么但它的威力在于“乘積”增長(zhǎng)。想象一下你有10種顏色的T恤和5種尺碼如果你想窮舉所有“顏色-尺碼”的組合那就是10乘以5共50種可能。這就是笛卡爾積在現(xiàn)實(shí)中的一個(gè)映射。但為什么我們需要了解它因?yàn)樗谟?jì)算機(jī)世界里無(wú)處不在且具有兩面性。一方面它是許多復(fù)雜操作如多表查詢、多重循環(huán)、組合生成的數(shù)學(xué)基礎(chǔ)另一方面它也是導(dǎo)致性能災(zāi)難、數(shù)據(jù)冗余的常見(jiàn)“坑點(diǎn)”。理解笛卡爾積不僅能幫你寫(xiě)出更高效的代碼更能讓你在設(shè)計(jì)數(shù)據(jù)交互時(shí)清晰地知道數(shù)據(jù)是如何“繁殖”的從而避免意料之外的系統(tǒng)崩潰或邏輯錯(cuò)誤。無(wú)論你是程序員、數(shù)據(jù)分析師還是產(chǎn)品經(jīng)理這都是一個(gè)繞不開(kāi)的基礎(chǔ)概念。2. 笛卡爾積的本質(zhì)集合的“暴力”配對(duì)2.1 數(shù)學(xué)定義與生活化類比笛卡爾積的嚴(yán)格數(shù)學(xué)定義是這樣的給定兩個(gè)集合A和B它們的笛卡爾積A × B是一個(gè)新的集合這個(gè)新集合里的每一個(gè)元素都是一個(gè)有序?qū)?a, b)其中a來(lái)自集合Ab來(lái)自集合B。用公式表示就是A × B { (a, b) | a ∈ A, b ∈ B }是不是有點(diǎn)抽象我們完全可以用更生活化的例子來(lái)理解。例子1點(diǎn)餐組合假設(shè)一家餐廳主食集合A {“米飯” “面條”}菜品集合B {“紅燒肉” “清蒸魚(yú)” “炒青菜”}。 那么所有可能的“一份主食一份菜”的組合就是它們的笛卡爾積 A × B { (“米飯” “紅燒肉”) (“米飯” “清蒸魚(yú)”) (“米飯” “炒青菜”) (“面條” “紅燒肉”) (“面條” “清蒸魚(yú)”) (“面條” “炒青菜”) } 一共是2主食 × 3菜品 6種組合。這就是菜單上所有單點(diǎn)搭配的理論基礎(chǔ)。例子2坐標(biāo)系統(tǒng)這是笛卡爾積最經(jīng)典的應(yīng)用。X軸上的點(diǎn)集合可以看作{1, 2, 3, …}Y軸上的點(diǎn)集合類似。整個(gè)二維平面上的每一個(gè)點(diǎn)比如(2, 5)其實(shí)就是X軸上的點(diǎn)“2”和Y軸上的點(diǎn)“5”組成的一個(gè)有序?qū)?。整個(gè)平面就是X軸和Y軸的笛卡爾積。這直接啟發(fā)了我們數(shù)據(jù)庫(kù)里用經(jīng)緯度表示地理位置用行號(hào)和列號(hào)定位Excel單元格。注意有序?qū)?a, b)和(b, a)是不同的除非a等于b。在坐標(biāo)里(2,5)和(5,2)是兩個(gè)不同的點(diǎn)。在數(shù)據(jù)庫(kù)關(guān)聯(lián)中(用戶ID, 訂單ID)和(訂單ID, 用戶ID)也代表不同的關(guān)聯(lián)關(guān)系。2.2 在計(jì)算機(jī)科學(xué)中的核心地位在計(jì)算機(jī)領(lǐng)域笛卡爾積不再是抽象的數(shù)學(xué)概念而是變成了實(shí)實(shí)在在的數(shù)據(jù)操作基石。數(shù)據(jù)庫(kù)SQL查詢這是最?!安瓤印钡牡胤?。當(dāng)你對(duì)兩個(gè)表進(jìn)行JOIN操作而沒(méi)有指定關(guān)聯(lián)條件即使用CROSS JOIN或漏寫(xiě)ON子句時(shí)數(shù)據(jù)庫(kù)引擎就會(huì)計(jì)算這兩個(gè)表的笛卡爾積。如果表A有1000行表B有2000行結(jié)果集將瞬間變成200萬(wàn)行這通常不是你想要的結(jié)果會(huì)急劇消耗內(nèi)存和CPU。編程中的多重循環(huán)最直觀的體現(xiàn)就是嵌套循環(huán)。colors [‘紅‘ ‘藍(lán)‘ ‘黃‘] sizes [‘S‘ ‘M‘ ‘L‘] for color in colors: for size in sizes: print(f“{color}{size}“)這段代碼的輸出就是colors和sizes兩個(gè)列表的笛卡爾積紅S、紅M、紅L、藍(lán)S…… 共9種組合。任何需要窮舉所有組合場(chǎng)景的算法底層邏輯都是笛卡爾積。軟件測(cè)試在正交測(cè)試法或配對(duì)測(cè)試中我們需要覆蓋多個(gè)參數(shù)的不同取值組合。如果對(duì)所有參數(shù)進(jìn)行全組合測(cè)試測(cè)試用例數(shù)就是各參數(shù)取值數(shù)量的乘積即笛卡爾積。為了減少用例數(shù)測(cè)試工程師會(huì)采用一些算法如All-Pairs來(lái)選取覆蓋大部分缺陷的、笛卡爾積的一個(gè)子集。數(shù)據(jù)結(jié)構(gòu)與算法在圖論中兩個(gè)圖的笛卡爾積可以生成新的圖。在編譯原理中狀態(tài)機(jī)的組合也可能涉及笛卡爾積運(yùn)算。理解笛卡爾積就是理解這種“組合爆炸”的根源。它提醒我們?cè)谔幚矶鄠€(gè)集合或數(shù)據(jù)源時(shí)必須明確它們之間的關(guān)系是“相乘”還是“關(guān)聯(lián)”。無(wú)意識(shí)的相乘就是災(zāi)難有意識(shí)的利用就是工具。3. 實(shí)戰(zhàn)解析SQL中的笛卡爾積“坑”與“用”3.1 災(zāi)難現(xiàn)場(chǎng)粗心導(dǎo)致的CROSS JOIN讓我們回到小張遇到的問(wèn)題用具體數(shù)據(jù)還原一下現(xiàn)場(chǎng)。 假設(shè)我們有兩個(gè)表employees員工表3條記錄 | id | name | |----|------| | 1 | 張三 | | 2 | 李四 | | 3 | 王五 |departments部門(mén)表2條記錄 | id | dept_name | |----|-----------| | 10 | 技術(shù)部 | | 20 | 市場(chǎng)部 |小張的本意可能是想看看員工和部門(mén)但他寫(xiě)下了這樣的SQLSELECT * FROM employees, departments;或者等價(jià)的SELECT * FROM employees CROSS JOIN departments;執(zhí)行結(jié)果會(huì)是employees.idemployees.namedepartments.iddepartments.dept_name1張三10技術(shù)部1張三20市場(chǎng)部2李四10技術(shù)部2李四20市場(chǎng)部3王五10技術(shù)部3王五20市場(chǎng)部看到了嗎3個(gè)員工和2個(gè)部門(mén)產(chǎn)生了 3 × 2 6 條記錄。每個(gè)員工都和每個(gè)部門(mén)強(qiáng)行配對(duì)了一次。這顯然不符合業(yè)務(wù)邏輯因?yàn)橐粋€(gè)員工通常只屬于一個(gè)部門(mén)。在真實(shí)場(chǎng)景中如果員工表有1萬(wàn)人部門(mén)表有100個(gè)這個(gè)查詢將瞬間生成100萬(wàn)條無(wú)意義的數(shù)據(jù)足以拖垮一個(gè)準(zhǔn)備不足的數(shù)據(jù)庫(kù)。實(shí)操心得在寫(xiě)JOIN語(yǔ)句時(shí)養(yǎng)成條件反射般的習(xí)慣——立即思考并寫(xiě)下關(guān)聯(lián)條件ON子句。對(duì)于INNER JOIN、LEFT JOIN等數(shù)據(jù)庫(kù)會(huì)強(qiáng)制你寫(xiě)但對(duì)于FROM A, B這種老式語(yǔ)法或CROSS JOIN編譯器不會(huì)報(bào)錯(cuò)全靠自覺(jué)。建議團(tuán)隊(duì)規(guī)范中明確禁用隱式的逗號(hào)連接表方式強(qiáng)制使用顯式的JOIN ... ON ...語(yǔ)法從源頭減少錯(cuò)誤。3.2 有用武之地刻意為之的笛卡爾積應(yīng)用當(dāng)然笛卡爾積并非總是洪水猛獸在特定場(chǎng)景下它是解決問(wèn)題的利器。場(chǎng)景一生成測(cè)試數(shù)據(jù)或全量組合比如你需要生成一個(gè)日期維度表包含2024年所有月份和所有產(chǎn)品類型的組合以便后續(xù)填充銷售計(jì)劃。-- 假設(shè)有月份表months(1-12)和產(chǎn)品類型表product_types(‘A‘ ‘B‘ ‘C‘) SELECT m.month, p.product_type FROM months m CROSS JOIN product_types p ORDER BY m.month, p.product_type;這會(huì)生成36條記錄為每個(gè)產(chǎn)品在每個(gè)月的計(jì)劃提供了“骨架”。場(chǎng)景二計(jì)算矩陣或網(wǎng)格在數(shù)據(jù)分析中有時(shí)需要計(jì)算兩個(gè)維度上所有點(diǎn)的指標(biāo)。例如計(jì)算不同年齡段和不同城市級(jí)別的用戶總數(shù)分布即使某些組合計(jì)數(shù)為0也需要展示。SELECT a.age_group, c.city_level, COUNT(u.id) as user_count FROM (SELECT ‘18-25‘ as age_group UNION ALL SELECT ‘26-35‘ ...) a CROSS JOIN (SELECT ‘一線‘ as city_level UNION ALL SELECT ‘二線‘ ...) c LEFT JOIN users u ON u.age BETWEEN ... AND ... AND u.city_level c.city_level GROUP BY a.age_group, c.city_level;這里我們先通過(guò)笛卡爾積生成所有“年齡段-城市級(jí)別”的理論組合矩陣再左連接實(shí)際用戶表進(jìn)行統(tǒng)計(jì)確保了結(jié)果集的完整性。場(chǎng)景三實(shí)現(xiàn)類似循環(huán)的復(fù)雜操作在某些數(shù)據(jù)庫(kù)不支持復(fù)雜循環(huán)時(shí)可以用笛卡爾積配合數(shù)字輔助表來(lái)模擬。例如將一個(gè)字符串按分隔符拆分成多行。-- 假設(shè)有一個(gè)數(shù)字輔助表numbers包含從1到足夠大的連續(xù)整數(shù) SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(‘a(chǎn)pple,banana,orange‘ ‘‘ n.id) ‘‘ -1) as fruit FROM numbers n CROSS JOIN (SELECT ‘a(chǎn)pple,banana,orange‘ as str) t WHERE n.id (LENGTH(t.str) - LENGTH(REPLACE(t.str ‘‘ ‘‘)) 1);通過(guò)和數(shù)字表做笛卡爾積并過(guò)濾實(shí)現(xiàn)了將一行數(shù)據(jù)“爆炸”成多行的效果。注意事項(xiàng)即使在刻意使用CROSS JOIN時(shí)也務(wù)必評(píng)估結(jié)果集大小。如果兩個(gè)源表很大產(chǎn)生的笛卡爾積將是天文數(shù)字務(wù)必加上嚴(yán)格的WHERE條件限制或使用LIMIT子句。在業(yè)務(wù)代碼中對(duì)于可能產(chǎn)生大笛卡爾積的操作應(yīng)考慮在應(yīng)用層分步計(jì)算或使用更高效的算法替代。4. 性能陷阱與深度優(yōu)化策略4.1 為什么笛卡爾積是性能殺手笛卡爾積的性能消耗主要來(lái)自兩個(gè)方面我們通過(guò)一個(gè)簡(jiǎn)單的復(fù)雜度分析來(lái)理解。假設(shè)有兩個(gè)集合/表表A 數(shù)據(jù)量記為M表B 數(shù)據(jù)量記為N時(shí)間復(fù)雜度生成笛卡爾積需要嵌套遍歷兩個(gè)集合。算法復(fù)雜度是O(M * N)。這意味著數(shù)據(jù)量呈線性增長(zhǎng)時(shí)計(jì)算量和結(jié)果集大小呈平方級(jí)增長(zhǎng)。當(dāng)M和N都達(dá)到百萬(wàn)級(jí)別時(shí)M*N就是萬(wàn)億級(jí)別這是任何單機(jī)系統(tǒng)都難以承受的。空間復(fù)雜度結(jié)果集需要存儲(chǔ) M * N 條記錄。每條記錄都包含A表和B表的所有字段。這會(huì)消耗巨大的內(nèi)存如果數(shù)據(jù)庫(kù)嘗試在內(nèi)存中處理或產(chǎn)生大量的臨時(shí)磁盤(pán)I/O如果使用臨時(shí)表嚴(yán)重?cái)D占系統(tǒng)資源。網(wǎng)絡(luò)與客戶端開(kāi)銷巨大的結(jié)果集從數(shù)據(jù)庫(kù)服務(wù)器傳輸?shù)綉?yīng)用服務(wù)器或客戶端會(huì)占用大量網(wǎng)絡(luò)帶寬并可能導(dǎo)致客戶端內(nèi)存溢出而崩潰。一個(gè)真實(shí)的估算案例 你有一個(gè)用戶日志表user_logs每日增量約1000萬(wàn)條保留7天共約7000萬(wàn)條和一個(gè)用戶屬性維度表user_dim5000萬(wàn)用戶。如果不小心在兩者之間漏寫(xiě)了關(guān)聯(lián)條件。潛在結(jié)果集行數(shù)70000000 * 50000000 3.5 * 10^153.5千萬(wàn)億行。假設(shè)每行數(shù)據(jù)僅100字節(jié)總數(shù)據(jù)量約為3.5 * 10^15 * 100 Bytes ≈ 3.5 * 10^17 Bytes ≈350 Petabytes350000 TB。 這完全超出了任何現(xiàn)有商用數(shù)據(jù)庫(kù)的處理能力查詢會(huì)直接掛起或拖垮整個(gè)數(shù)據(jù)庫(kù)集群。4.2 識(shí)別與排查笛卡爾積問(wèn)題在復(fù)雜的SQL查詢中笛卡爾積有時(shí)會(huì)隱藏得很深尤其是在關(guān)聯(lián)多個(gè)表超過(guò)3個(gè)且關(guān)聯(lián)條件復(fù)雜時(shí)。以下是一些識(shí)別和排查的技巧查看執(zhí)行計(jì)劃EXPLAIN這是最權(quán)威的手段。在SQL語(yǔ)句前加上EXPLAIN或EXPLAIN ANALYZE來(lái)查看數(shù)據(jù)庫(kù)的執(zhí)行計(jì)劃。重點(diǎn)關(guān)注JOIN類型。如果看到CROSS JOIN且沒(méi)有對(duì)應(yīng)的ON條件或Using where過(guò)濾那很可能就是笛卡爾積。觀察預(yù)估的行數(shù)rows列。如果某個(gè)步驟的預(yù)估行數(shù)異常巨大例如是兩個(gè)前驅(qū)步驟rows值的乘積那就是一個(gè)強(qiáng)烈的警告信號(hào)。進(jìn)行數(shù)據(jù)沙盒測(cè)試在開(kāi)發(fā)或測(cè)試環(huán)境先用LIMIT子句對(duì)每個(gè)大表進(jìn)行采樣。-- 危險(xiǎn)查詢 SELECT COUNT(*) FROM big_table_a, big_table_b WHERE ...; -- 安全測(cè)試 SELECT COUNT(*) FROM (SELECT * FROM big_table_a LIMIT 10) a, (SELECT * FROM big_table_b LIMIT 10) b WHERE ...;如果加上LIMIT后查詢飛快而去掉后卡死基本可以斷定是產(chǎn)生了大結(jié)果集的笛卡爾積或錯(cuò)誤的關(guān)聯(lián)。審視關(guān)聯(lián)條件確保每個(gè)JOIN都有對(duì)應(yīng)的ON條件。檢查WHERE子句中的條件是否足以將多表“連接”起來(lái)。有時(shí)WHERE a.id b.id被誤寫(xiě)成WHERE a.id a.id恒真或WHERE a.id b.id OR b.name is nullOR條件可能導(dǎo)致優(yōu)化器選擇不同的執(zhí)行計(jì)劃。特別注意“一對(duì)多”再“多對(duì)一”的鏈?zhǔn)疥P(guān)聯(lián)確保路徑是閉合的沒(méi)有形成環(huán)狀依賴導(dǎo)致重復(fù)計(jì)算。4.3 高級(jí)優(yōu)化與替代方案當(dāng)業(yè)務(wù)確實(shí)需要處理類似笛卡爾積的全組合邏輯時(shí)我們也不能因噎廢食而是需要更聰明的策略。分治與批處理將大問(wèn)題拆分成小問(wèn)題。例如需要計(jì)算所有用戶對(duì)之間的相似度這本質(zhì)上是用戶表對(duì)自己的笛卡爾積。不要一次性計(jì)算而是按用戶分組或分區(qū)分批計(jì)算。比如今天計(jì)算ID為1-10000的用戶與其他所有用戶的相似度明天計(jì)算10001-20000的以此類推。利用數(shù)據(jù)庫(kù)的窗口函數(shù)或?qū)S姓Z(yǔ)法某些復(fù)雜的“矩陣”計(jì)算可以用窗口函數(shù)替代。例如計(jì)算每個(gè)部門(mén)工資相對(duì)于公司平均工資的排名不需要將員工表和公司平均值表做笛卡爾積使用AVG() OVER()即可。在應(yīng)用層進(jìn)行組合計(jì)算如果邏輯允許將數(shù)據(jù)從數(shù)據(jù)庫(kù)取出已經(jīng)是過(guò)濾和聚合后的較小結(jié)果集在應(yīng)用層的內(nèi)存中完成組合運(yùn)算?,F(xiàn)代應(yīng)用服務(wù)器的內(nèi)存和CPU能力很強(qiáng)且編程語(yǔ)言如Python的itertools.product對(duì)此有高效實(shí)現(xiàn)比在數(shù)據(jù)庫(kù)中進(jìn)行大規(guī)模笛卡爾積更可控。使用專門(mén)的大數(shù)據(jù)處理引擎對(duì)于超大規(guī)模的數(shù)據(jù)組合需求如推薦系統(tǒng)的協(xié)同過(guò)濾必須求助于Hadoop、Spark等分布式計(jì)算框架。它們可以將計(jì)算任務(wù)分解到數(shù)百上千臺(tái)機(jī)器上并行處理從而解決單機(jī)無(wú)法承受的笛卡爾積計(jì)算。在Spark中你可以使用cartesian轉(zhuǎn)換但必須清楚其代價(jià)并確保有足夠的集群資源。核心避坑技巧對(duì)于線上核心查詢建立行數(shù)閾值告警。在數(shù)據(jù)庫(kù)監(jiān)控或APM應(yīng)用性能管理工具中設(shè)置規(guī)則如果單個(gè)查詢返回的行數(shù)超過(guò)一個(gè)預(yù)設(shè)值例如10萬(wàn)行立即觸發(fā)告警。這可以幫助你快速發(fā)現(xiàn)那些因條件缺失或錯(cuò)誤而產(chǎn)生的、未被察覺(jué)的笛卡爾積查詢?cè)谟绊憯U(kuò)大前及時(shí)干預(yù)。5. 思維延伸超越數(shù)據(jù)庫(kù)的笛卡爾積思維理解笛卡爾積更重要的是建立一種“組合爆炸”的思維模型這種模型能幫你預(yù)防和解決許多系統(tǒng)設(shè)計(jì)問(wèn)題。5.1 在系統(tǒng)架構(gòu)設(shè)計(jì)中的應(yīng)用微服務(wù)間的API調(diào)用假設(shè)你有一個(gè)訂單服務(wù)和一個(gè)用戶服務(wù)。前端一個(gè)頁(yè)面需要展示100個(gè)訂單的詳情每個(gè)訂單都需要顯示下單用戶的基本信息。一種低效的做法是訂單服務(wù)查詢到100個(gè)訂單后循環(huán)調(diào)用100次用戶服務(wù)的GET /user/{id}接口。這就是一種“類笛卡爾積”的思維陷阱——將兩個(gè)服務(wù)的數(shù)據(jù)進(jìn)行了一次低效的“相乘”。優(yōu)化方案改為批量查詢接口。訂單服務(wù)收集所有用戶ID去重后可能只有幾十個(gè)一次調(diào)用用戶服務(wù)的POST /users/batch接口獲取這批用戶的映射表然后在內(nèi)存中進(jìn)行組合。這極大地減少了網(wǎng)絡(luò)開(kāi)銷和服務(wù)負(fù)載。緩存鍵設(shè)計(jì)如果你的緩存鍵由多個(gè)變量組合而成例如user:{userId}:page:{pageNum}:size:{pageSize}那么不同的參數(shù)組合會(huì)產(chǎn)生大量的緩存鍵。如果參數(shù)取值范圍大就可能產(chǎn)生笛卡爾積式的鍵空間導(dǎo)致緩存內(nèi)存被快速撐滿或緩存命中率低下。優(yōu)化方案考慮對(duì)參數(shù)進(jìn)行歸一化或分段。例如將pageSize固定為幾個(gè)標(biāo)準(zhǔn)值如20 50 100而不是任意整數(shù)?;蛘呤褂酶酆系木彺骀I并在應(yīng)用層進(jìn)行二次過(guò)濾。5.2 在算法與業(yè)務(wù)邏輯中的體現(xiàn)嵌套循環(huán)的優(yōu)化這是最直接的體現(xiàn)。當(dāng)你寫(xiě)for i in list_a: for j in list_b:的時(shí)候你就要立刻意識(shí)到這是O(n2)的復(fù)雜度。思考是否必須能否先用哈希表字典預(yù)處理其中一個(gè)集合將復(fù)雜度降為O(n)# 低效查找list_a和list_b中id相同的項(xiàng) for a in list_a: for b in list_b: if a[‘id‘] b[‘id‘]: # do something # 高效使用字典降維 dict_b {item[‘id‘]: item for item in list_b} for a in list_a: b dict_b.get(a[‘id‘]) if b: # do something產(chǎn)品功能與權(quán)限矩陣設(shè)計(jì)一個(gè)后臺(tái)管理系統(tǒng)有10個(gè)功能模塊每個(gè)模塊有4種操作權(quán)限增、刪、改、查。如果為每個(gè)用戶直接配置那就是10*440個(gè)配置點(diǎn)。這就是權(quán)限和功能的笛卡爾積。通常的解決方案是引入“角色”概念先定義好少數(shù)幾個(gè)角色如管理員、編輯、訪客每個(gè)角色擁有一個(gè)權(quán)限集合。用戶只需關(guān)聯(lián)一個(gè)角色從而將配置復(fù)雜度從用戶數(shù) * 40降低到用戶數(shù) * 1 角色數(shù) * 40。5.3 一個(gè)綜合案例商品SKU的生成與管理在電商系統(tǒng)中商品SKU庫(kù)存量單位是笛卡爾積思維的典型應(yīng)用。一件衣服有顏色紅、藍(lán)、尺碼S、M、L、材質(zhì)棉、滌綸三個(gè)屬性。那么理論上它對(duì)應(yīng)的SKU數(shù)量就是 2 * 3 * 2 12個(gè)。后臺(tái)系統(tǒng)在管理時(shí)有兩種設(shè)計(jì)模式模式一預(yù)生成在創(chuàng)建商品時(shí)根據(jù)屬性組合直接調(diào)用笛卡爾積算法生成12個(gè)具體的SKU記錄存入數(shù)據(jù)庫(kù)。查詢庫(kù)存、下單扣減都非常直接。模式二動(dòng)態(tài)計(jì)算只保存商品和獨(dú)立的屬性值。當(dāng)用戶選擇“紅色、M碼、棉”時(shí)系統(tǒng)動(dòng)態(tài)計(jì)算并定位到對(duì)應(yīng)的SKU。這種方式更靈活例如新增一個(gè)顏色屬性不需要重構(gòu)所有SKU但查詢邏輯更復(fù)雜。選擇哪種模式取決于業(yè)務(wù)規(guī)模、屬性變更頻率和技術(shù)架構(gòu)。理解笛卡爾積在這里的作用能幫助產(chǎn)品經(jīng)理和工程師做出更合理的權(quán)衡。我個(gè)人在多年的開(kāi)發(fā)和數(shù)據(jù)工作中一個(gè)深刻的體會(huì)是很多復(fù)雜的系統(tǒng)問(wèn)題追根溯源往往都能簡(jiǎn)化成對(duì)若干集合之間關(guān)系的錯(cuò)誤處理。要么是該用笛卡爾積全組合的地方用了普通關(guān)聯(lián)導(dǎo)致數(shù)據(jù)缺失要么是該用關(guān)聯(lián)的地方意外產(chǎn)生了笛卡爾積導(dǎo)致數(shù)據(jù)爆炸。建立起對(duì)“數(shù)據(jù)關(guān)系維度”的敏感度在寫(xiě)JOIN、設(shè)計(jì)循環(huán)、規(guī)劃接口時(shí)心里先默默算一下可能的數(shù)量級(jí)這個(gè)習(xí)慣能幫你避開(kāi)一大半的性能陷阱和邏輯Bug。下次當(dāng)你看到查詢突然變慢或者內(nèi)存無(wú)故飆升時(shí)不妨第一個(gè)想到“我是不是不小心制造了一個(gè)笛卡爾積”