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