Java八股文——MySQL「SQL 基礎篇」

NOSQL和SQL的區別?

面試官您好,SQL(關系型數據庫)和NoSQL(非關系型數據庫)是當今數據存儲領域的兩大主流陣營。它們之間不是“誰取代誰”的關系,而是兩種完全不同的設計哲學,適用于解決不同類型的問題

我通常會從以下幾個核心維度來對比它們:

1. 數據模型 (Data Model) —— 結構化 vs. 靈活
  • SQL (關系型數據庫)

    • 核心:基于關系模型,數據被組織在結構化的二維表(Table)中,表有固定的模式(Schema),包含行(Row)和列(Column)。
    • 特點強Schema。在寫入數據之前,必須先定義好表的結構(字段名、數據類型、約束等)。所有存入的數據都必須嚴格遵守這個結構。
    • 例子:MySQL, PostgreSQL, Oracle, SQL Server。
  • NoSQL (非關系型數據庫)

    • 核心:數據模型非常多樣和靈活,沒有固定的模式。
    • 主要類型
      1. 鍵值存儲 (Key-Value):數據以簡單的{key: value}形式存儲,非常高效。代表:Redis, Memcached
      2. 文檔存儲 (Document):數據以類似JSON或BSON的文檔格式存儲,結構靈活,可以嵌套。代表:MongoDB
      3. 列族存儲 (Column-Family):數據按列族組織,非常適合海量數據的讀寫和分析。代表:HBase, Cassandra
      4. 圖存儲 (Graph):專門用于存儲和查詢圖結構數據(節點、邊、屬性),如社交網絡關系。代表:Neo4j
    • 特點動態Schema無Schema。可以隨時向數據中添加新的字段,無需預先定義。
2. 事務與一致性 (Transaction & Consistency)
  • SQL

    • 核心:遵循ACID原則(原子性、一致性、隔離性、持久性)。
    • 特點:提供非常強大的事務支持,能夠保證數據的強一致性。非常適合對數據準確性要求極高的場景。
  • NoSQL

    • 核心:通常遵循BASE理論(基本可用、軟狀態、最終一致性)。
    • 特點:為了追求更高的性能和可用性,通常會犧牲強一致性,轉而支持最終一致性。它們對事務的支持通常較弱,或者只支持單行/單文檔的原子操作。
3. 擴展性 (Scalability)
  • SQL

    • 擴展方式:通常通過垂直擴展(Scale-up) 來實現,即提升單個服務器的硬件性能(更強的CPU、更大的內存、更快的硬盤)。
    • 水平擴展(Scale-out):雖然也可以通過分庫分表、讀寫分離等方式實現水平擴展,但實現起來相對復雜,且對應用層有侵入。
  • NoSQL

    • 擴展方式天生為分布式和水平擴展而設計
    • 特點:可以非常容易地通過增加更多的普通服務器節點來線性地提升整個集群的性能和存儲容量。它們的架構通常內置了數據分片(Sharding)和副本(Replication)機制。
4. 查詢語言 (Query Language)
  • SQL

    • 使用標準化的SQL(Structured Query Language) 進行查詢。SQL功能強大,支持復雜的多表連接(JOIN)、聚合、分組等操作。
  • NoSQL

    • 沒有統一的查詢語言。每種NoSQL數據庫都有自己獨特的查詢API或查詢語言。它們的查詢能力通常不如SQL強大,特別是不擅長處理復雜的多表連接查詢

總結與選型建議

特性SQL (關系型)NoSQL (非關系型)
數據模型結構化 (二維表, 強Schema)多樣化 (Key-Value, 文檔等, 弱/無Schema)
一致性ACID (強一致性)BASE (最終一致性)
擴展性垂直擴展 (Scale-up) 為主水平擴展 (Scale-out) 為主
事務強大弱或不支持
適用場景事務性強的、數據關系復雜的應用高并發、海量數據、結構不固定的應用

我的選型策略

  • 什么時候選擇SQL數據庫?

    • 當業務需要強大的事務保證時,比如金融、電商的訂單和支付系統
    • 當數據之間存在復雜的關系,需要進行頻繁的連接查詢時。
    • 當數據結構相對穩定,不需要頻繁變更時。
  • 什么時候選擇NoSQL數據庫?

    • 當需要極高的讀寫性能和海量數據存儲時,比如社交網絡的信息流、物聯網的傳感器數據
    • 當數據結構不固定、經常需要變化時,比如用戶畫像、商品屬性等。
    • 當系統需要極高的可用性和易于水平擴展時。
    • 作為關系型數據庫的補充,比如用Redis來做MySQL的熱點數據緩存。

在現代架構中,我們通常不會只選擇一種,而是將SQL和NoSQL數據庫組合使用,讓它們各自在最擅長的領域發揮作用,以構建出更健壯、性能更高的系統。


數據庫三大范式是什么?

面試官您好,數據庫的三大范式(Normal Forms, NF)是我們在進行關系型數據庫邏輯設計時,所遵循的一套基本準則和規范

它的核心目標,是通過對表結構的合理設計,來減少數據冗余、避免數據異常(如插入異常、更新異常、刪除異常),從而保證數據的一致性和完整性

我來分別解釋一下這三大范式,并用一個例子來貫穿。

假設我們有一個未優化的“訂單信息表”:

原始表:訂單表 (Order_Info)

訂單ID顧客姓名顧客電話商品ID商品名稱商品單價購買數量
O001張三138…P01手機50001
O001張三138…P02耳機2002
O002李四139…P01手機50001
第一范式 (1NF): 保證字段的原子性
  • 定義:要求數據庫表中的每一個字段(列)都是不可再分的原子值
  • 核心思想:確保“一格一值”,不能在一個單元格里存儲多個值。
  • 如何判斷:我們看上面的原始表,每個字段都只包含一個單一的數據,沒有像“手機,耳機”這樣存儲在一個單元格里的情況。所以,這個原始表已經滿足了第一范式
  • 反例:如果我們設計一個商品字段,里面存的是"P01:手機:5000, P02:耳機:200",這就違反了1NF。
第二范式 (2NF): 消除對主鍵的部分函數依賴
  • 定義:在滿足第一范式的基礎上,要求表中的每一個非主鍵字段,都必須完全依賴于整個主鍵,而不是只依賴于主鍵的一部分
  • 核心思想:確保“有事兒找老大,別找副手”。這個范式只針對聯合主鍵的情況。如果表是單主鍵,那么它只要滿足1NF,就自動滿足2NF。
  • 如何分析我們的例子
    • 在原始表中,要唯一確定一條記錄(比如張三買手機這條),需要 (訂單ID, 商品ID) 作為聯合主鍵。
    • 我們來分析非主鍵字段:
      • 購買數量:它既依賴于訂單ID,也依賴于商品ID,是完全依賴
      • 顧客姓名, 顧客電話:它們只依賴于訂單ID,與商品ID無關。這是部分依賴
      • 商品名稱, 商品單價:它們只依賴于商品ID,與訂單ID無關。這也是部分依賴
    • 結論:存在部分依賴,所以不滿足第二范式
  • 如何改造(拆分)
    • 將部分依賴的字段拆分出去,形成新的表。
    • 訂單表 (Orders): (主鍵: 訂單ID)
      訂單ID顧客姓名顧客電話
      O001張三138…
      O002李四139…
    • 商品表 (Products): (主鍵: 商品ID)
      商品ID商品名稱商品單價
      P01手機5000
      P02耳機200
    • 訂單詳情表 (Order_Details): (聯合主鍵: 訂單ID, 商品ID)
      訂單ID商品ID購買數量
      O001P011
      O001P022
      O002P011
  • 好處:現在,顧客姓名商品名稱等信息不再冗余存儲,更新時也不會出現數據不一致的問題。
第三范式 (3NF): 消除對主鍵的傳遞函數依賴
  • 定義:在滿足第二范式的基礎上,要求表中的任何非主鍵字段,都不能依賴于其他非主鍵字段

  • 核心思想:確保所有非主鍵字段都直接依賴于主鍵,而不是通過“跳板”間接依賴。

  • 如何分析我們的例子

    • 我們看改造后的訂單表 (Orders)顧客姓名顧客電話是直接依賴于主鍵訂單ID嗎?
    • 不完全是。更準確地說,訂單ID決定了是哪個顧客,而顧客才決定了他的姓名和電話。這里存在一個傳遞依賴訂單ID -> 顧客 -> (顧客姓名, 顧客電話)
    • 結論:存在傳遞依賴,所以不滿足第三范式
  • 如何改造(再次拆分)

    • 將傳遞依賴的字段也拆分出去。
    • 顧客表 (Customers): (主鍵: 顧客ID)
      顧客ID顧客姓名顧客電話
      C01張三138…
      C02李四139…
    • 最終的訂單表 (Orders): (主鍵: 訂單ID)
      訂單ID顧客ID (外鍵)
      O001C01
      O002C02
  • 好處:現在,顧客的信息是獨立維護的,如果一個顧客改了電話,我們只需要修改Customers表的一行,所有與他相關的訂單信息都能保持一致。

總結與實踐

  • 1NF -> 2NF:解決部分依賴,要求每個非主鍵字段完全依賴于整個主鍵。
  • 2NF -> 3NF:解決傳遞依賴,要求每個非主鍵字段直接依賴于主鍵。

在實際的數據庫設計中,我們通常會力求滿足第三范式(3NF),這能最大程度地減少數據冗余,保證數據一致性。但有時,為了查詢性能,我們也會進行 “反范式化” 設計,適度地增加一些冗余字段來避免復雜的多表連接(JOIN)查詢,這是一種在“數據一致性”和“查詢效率”之間的權衡。


MySQL 怎么連表查詢?

面試官您好,在MySQL(以及大多數關系型數據庫)中,當我們需要從多個關聯的表中獲取數據時,就需要使用連接查詢(JOIN)。

最核心的連接查詢主要有以下幾種。我們可以用兩張簡單的示例表來直觀地理解它們的區別:

students 表 (學生表)

idnameclass_id
1張三101
2李四102
3王五103

classes 表 (班級表)

idname
101一班
102二班
104四班
1. 內連接 (INNER JOIN) —— “取交集”
  • 定義:這是最常用的一種連接。它只返回兩個表中連接字段能夠匹配上的行。可以理解為取兩個表的交集
  • SQL示例
    SELECT s.name AS student_name, c.name AS class_name
    FROM students s
    INNER JOIN classes c ON s.class_id = c.id;
    
  • 結果
    student_nameclass_name
    張三一班
    李四二班
    • (王五因為class_id=103classes表中找不到匹配,被排除;四班因為在students表中沒有學生關聯,也被排除。)
2. 左外連接 (LEFT JOIN / LEFT OUTER JOIN) —— “左表為王”
  • 定義:它會返回左表(FROM子句后的第一個表)的所有行,即使在右表中沒有匹配的記錄。
  • 工作機制:以左表為基礎,去右表中查找匹配的行。
    • 如果找到了匹配行,就將右表的字段合并進來。
    • 如果沒找到匹配行,右表的所有字段將顯示為 NULL
  • SQL示例
    SELECT s.name AS student_name, c.name AS class_name
    FROM students s
    LEFT JOIN classes c ON s.class_id = c.id;
    
  • 結果
    student_nameclass_name
    張三一班
    李四二班
    王五NULL
3. 右外連接 (RIGHT JOIN / RIGHT OUTER JOIN) —— “右表為王”
  • 定義:與左連接相反,它會返回右表(JOIN子句后的表)的所有行,即使在左表中沒有匹配的記錄。
  • 工作機制:以右表為基礎,去左表中查找。如果左表沒找到匹配,則左表的字段顯示為NULL
  • SQL示例
    SELECT s.name AS student_name, c.name AS class_name
    FROM students s
    RIGHT JOIN classes c ON s.class_id = c.id;
    
  • 結果
    student_nameclass_name
    張三一班
    李四二班
    NULL四班
4. 全外連接 (FULL OUTER JOIN) —— “合二為一,寧缺毋濫”
  • 定義:它會返回兩個表中的所有行。如果某一行在另一個表中沒有匹配,那么另一個表的字段將顯示為NULL。可以看作是左連接和右連接結果的并集
  • 注意MySQL本身不直接支持FULL OUTER JOIN關鍵字。但我們可以通過 LEFT JOIN UNION RIGHT JOIN 來模擬實現。
  • 模擬SQL示例
    SELECT s.name AS student_name, c.name AS class_name FROM students s LEFT JOIN classes c ON s.class_id = c.id
    UNION
    SELECT s.name AS student_name, c.name AS class_name FROM students s RIGHT JOIN classes c ON s.class_id = c.id;
    
  • 結果
    student_nameclass_name
    張三一班
    李四二班
    王五NULL
    NULL四班

其他連接類型

  • 交叉連接 (CROSS JOIN):返回兩個表的笛卡爾積,即第一個表的每一行都與第二個表的每一行進行組合。這在沒有ON條件時,是INNER JOIN的默認行為,通常需要謹慎使用。
  • 自連接 (SELF JOIN):指一個表與它自己進行連接。這在處理表內具有層級關系(如員工與經理、區域的父子關系)的數據時非常有用。

總結一下,在選擇連接方式時,我主要考慮:

  • 是否只需要兩個表中都能匹配上的數據?-> 用 INNER JOIN
  • 是否需要保留一個表的所有數據,而不管另一個表有沒有匹配?-> 用 LEFT JOINRIGHT JOIN
  • 是否需要兩個表的所有數據都展示出來?-> 用 FULL OUTER JOIN(在MySQL中用UNION模擬)。

MySQL如何避免重復插入數據?

面試官您好,在MySQL中避免重復插入數據,是一個保證數據唯一性和完整性的核心問題。我會從數據庫表結構層面(治本)SQL語句層面(治標) 這兩個維度來設計解決方案。

方案一:數據庫表結構層面 —— 建立唯一性約束 (治本之策)

這是最根本、最可靠的解決方案,它利用數據庫自身的能力來強制保證數據的唯一性。

  1. 設置主鍵 (PRIMARY KEY)

    • 作用:主鍵本身就具有唯一且非空的特性。如果我們的重復數據是基于某個ID字段來判斷的,那么將這個ID字段設為主鍵,就是最直接的辦法。任何試圖插入重復主鍵值的操作,都會被數據庫直接拒絕,并返回一個錯誤。
    • 缺點:一張表只能有一個主鍵。
  2. 建立唯一索引 (UNIQUE INDEX / UNIQUE KEY)

    • 作用:這是最常用、最靈活的方式。我們可以為一個或多個字段的組合,創建一個唯一索引。
    • 單字段唯一:比如,在用戶表中,usernameemail字段必須是唯一的。
      -- 創建表時定義
      CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50) NOT NULL,email VARCHAR(100),UNIQUE KEY `uk_username` (`username`),UNIQUE KEY `uk_email` (`email`)
      );-- 或為已存在的表添加
      ALTER TABLE users ADD UNIQUE INDEX `uk_username` (`username`);
      
    • 多字段聯合唯一:比如,在一個“用戶-角色”關聯表中,一個用戶只能擁有一個特定的角色一次。(user_id, role_id)這個組合必須是唯一的。
      CREATE TABLE user_roles (user_id INT,role_id INT,UNIQUE KEY `uk_user_role` (`user_id`, `role_id`)
      );
      
  • 優點
    • 絕對可靠:由數據庫層面保證,無論應用邏輯如何,重復數據都無法插入。
    • 性能高:唯一索引本身也能加速對這些字段的查詢。
方案二:SQL語句層面 —— 在插入時進行判斷和處理

有時候,我們不希望插入重復數據時直接拋出異常,而是希望有一些更優雅的處理方式,比如“如果存在就更新,不存在就插入”。這時,我們可以使用特定的SQL語法。

  1. INSERT IGNORE INTO

    • 作用:當執行INSERT操作時,如果因為唯一鍵沖突(主鍵或唯一索引)而導致插入失敗,這條INSERT語句會被默默地忽略掉,不會產生任何錯誤。
    • SQL示例
      INSERT IGNORE INTO users (username, email) VALUES ('admin', 'admin@example.com');
      
    • 適用場景:只關心“數據必須存在”,不關心是新插入的還是已存在的,并且不希望程序因為重復插入而中斷。
  2. REPLACE INTO

    • 作用:這是一個比較“暴力”的方式。當執行REPLACE操作時,如果唯一鍵沖突,它會先刪除那條舊的記錄,然后再插入一條新的記錄
    • SQL示例
      REPLACE INTO users (id, username, email) VALUES (1, 'admin_new', 'admin_new@example.com');
      
    • 警告:這個操作的本質是DELETE + INSERT,如果表上有觸發器,會先后觸發刪除和插入的觸發器。并且,如果id是自增的,它會消耗一個新的ID,而不是更新舊的。需要非常謹慎使用。
  3. INSERT INTO ... ON DUPLICATE KEY UPDATE (推薦)

    • 作用:這是最常用、最靈活、最推薦的“存在即更新”方案。
    • 工作機制:當執行INSERT操作時,如果發生唯一鍵沖突,它不會報錯,而是會轉而去執行UPDATE子句中指定的更新邏輯
    • SQL示例:假設我們想在用戶登錄時,更新其最后登錄時間,如果用戶不存在就創建。
      INSERT INTO users (username, last_login_time) 
      VALUES ('admin', NOW())
      ON DUPLICATE KEY UPDATE 
      last_login_time = NOW();
      
    • 優點:一條SQL就優雅地實現了“Insert or Update”(也叫Upsert)的邏輯,非常高效和方便。

總結與最佳實踐

  • 第一道防線(必須有):在數據庫表上建立唯一鍵約束(主鍵或唯一索引)。這是保證數據完整性的根本,不能依賴應用層的邏輯。
  • 第二道防線(按需選擇):在應用代碼中,根據業務需求選擇合適的SQL處理方式:
    • 希望存在就忽略,不存在就插入 -> 使用 INSERT IGNORE
    • 希望存在就更新,不存在就插入 -> 強烈推薦使用 ON DUPLICATE KEY UPDATE
    • 在寫操作之前,先執行一次SELECT來判斷數據是否存在。這在并發量低時可行,但在高并發下,從SELECTINSERT之間存在時間窗口,可能導致競態條件,不推薦。

通過這兩層防線的結合,我們就能非常健壯地處理數據重復插入的問題。

CHAR和 VARCHAR有什么區別?

面試官您好,CHARVARCHAR是MySQL中最常用的兩種字符串類型,它們最核心的區別在于其長度的存儲和處理方式,這個區別直接導致了它們在存儲空間、性能和適用場景上的巨大差異。

1. 核心區別:定長 vs. 變長
  • CHAR(N) (定長)

    • 定義CHAR是一種固定長度的字符串類型。當我們定義一個CHAR(10)的字段時,無論我們實際存入的數據是"abc"(3個字符)還是"hello"(5個字符),它在數據庫中永遠都會占用10個字符的存儲空間
    • 存儲機制:如果存入的數據長度小于N,MySQL會在其右側用空格進行填充,以補足到指定的長度。在讀取時,這些尾部的空格通常會被自動去除(除非SQL_MODE有特殊設置)。
  • VARCHAR(N) (變長)

    • 定義VARCHAR是一種可變長度的字符串類型。N在這里代表的是最大長度
    • 存儲機制:它只會根據實際存入的數據長度來分配存儲空間。但除了數據本身,它還需要額外的1到2個字節來記錄其實際長度。
      • 如果最大長度N小于等于255,用1個字節記錄長度。
      • 如果最大長度N大于255,用2個字節記錄長度。
2. 存儲空間與性能的權衡
特性CHAR(N)VARCHAR(N)
空間開銷固定,可能浪費空間可變,通常更節省空間
更新效率更高、更穩定可能導致性能問題
碎片化不易產生容易產生
  • 空間對比

    • 當存儲的字符串長度非常接近甚至就等于N時,CHAR可能比VARCHAR更節省空間,因為它省去了那1-2個字節的長度記錄開銷。
    • 當存儲的字符串長度差異很大時,VARCHAR的優勢就非常明顯了,能極大地節約磁盤空間。
  • 性能對比(這是一個更深入的考量點)

    • CHAR的優勢:因為長度固定,CHAR類型的字段在進行更新(UPDATE)操作時,通常不會改變記錄的物理長度。這使得數據庫在原地更新數據變得非常容易,不容易導致行遷移(Row Migration)頁分裂(Page Split),因此更新性能更穩定。
    • VARCHAR的劣勢:如果一個VARCHAR字段的值從一個短字符串更新為一個長字符串(比如從"hi"更新為"hello world"),導致該行的總長度超出了當前數據頁的剩余空間,就可能觸發代價高昂的頁分裂操作,從而影響性能。
3. 選型建議與最佳實踐

基于以上對比,我的選型策略非常明確:

  • 什么時候選擇CHAR

    1. 當字段的長度是固定不變或幾乎不變的時候。這是CHAR最理想的場景。
      • 典型例子:MD5哈希值(固定32位)、UUID(固定36位)、性別(‘M’/‘F’)、郵政編碼、身份證號等。
    2. 當存儲的字符串非常短時(比如1-10個字符),即使長度可變,使用CHAR的性能和空間優勢也可能超過VARCHAR
  • 什么時候選擇VARCHAR

    1. 絕大多數場景下,當字段的長度是可變的,都應該使用VARCHAR
      • 典型例子:用戶名、商品標題、文章內容、備注信息等。
    2. 在使用VARCHAR時,一個重要的最佳實踐是N設置一個合理的、盡可能小的最大長度。比如,一個用戶名字段,設置成VARCHAR(50)就比VARCHAR(255)要好得多。這不僅能節省空間,還能利用到MySQL的內存優化(比如在排序時可以使用內存臨時表)。

總結一下CHAR追求的是處理速度和性能的穩定性,以可能浪費空間為代價;而VARCHAR追求的是空間的極致利用,以可能在更新時產生一些性能開銷為代價。在實際設計中,我們需要根據數據的具體特性來做出最合適的選擇。

VARCHAR后面代表字節還是字符?

面試官您好,這是一個非常好的細節問題,也是很多開發者容易混淆的地方。

在現代的MySQL版本(5.0及以后)中,VARCHAR(N)括號里的數字N明確代表的是“字符數”(Character Count)

1. 核心概念:字符 vs. 字節
  • 字符:是我們人類語言中最小的意義單位,比如一個英文字母'A'、一個數字'1'、或者一個漢字'中',都算作一個字符
  • 字節:是計算機中存儲數據的基本單位。一個字符究竟占用多少個字節,完全取決于數據庫所使用的字符集(Charset)
2. 字符集的影響

正如您所說,不同的字符集,每個字符占用的字節數是不同的:

  • latin1ascii 字符集

    • 每個字符(主要是英文字母、數字、標點)都只占用1個字節
    • 在這種情況下,VARCHAR(10)最多能存10個字符,最大占用10個字節。
  • gbk 字符集

    • 一個英文字母占用1個字節,一個漢字占用2個字節
    • VARCHAR(10)仍然能存10個字符,比如10個漢字,此時它會占用10 * 2 = 20個字節。
  • utf8mb4 字符集 (現在最推薦的通用字符集)

    • 這是一個可變長度的字符集。一個英文字母占用1個字節,一個常用漢字占用3個字節,而一些生僻字或Emoji表情可能占用4個字節
    • 對于VARCHAR(10),它可以存:
      • 10個英文字母(占用10字節)。
      • 10個常用漢字(占用30字節)。
      • 10個Emoji表情(占用40字節)。
      • 或者它們的任意組合,只要總字符數不超過10
3. 存儲開銷的完整計算

VARCHAR的總物理存儲開銷,等于真實數據的字節數,再加上1到2個字節用于記錄長度的“前綴”

  • 總開銷 = (真實數據的字節數) + (1或2字節的長度前綴)
4. 歷史演進與一個重要的“邊界”
  • 歷史演進:值得一提的是,在非常古老的MySQL版本(4.1之前),VARCHAR(N)中的N確實指的是字節數。但現在我們使用的版本,都已經統一為字符數了。
  • 邊界限制VARCHARN雖然理論上最大可以設置到65535,但實際上它會受到MySQL單行最大長度(65535字節) 的限制。
    • 比如,在一張utf8mb4編碼的表中,由于一個字符最多可能占用4個字節,所以你最多只能定義一個VARCHAR(16383)左右的字段(16383 * 4約等于65532),因為還要給其他字段和一些內部開銷留出空間。

總結一下VARCHAR(N)中的N字符數,這是一個非常人性化的設計,因為它讓我們在定義字段時,可以更專注于業務含義(比如“用戶名最多20個字”),而不需要去過多地關心底層不同字符集導致的字節換算問題。但我們在設置N的大小時,也需要對字符集有一個基本的了解,以便估算其可能占用的最大物理空間。

INT(1) 和 INT(10) 在MySQL中有什么不同?

面試官您好,INT(1)INT(10)的區別,是MySQL中一個極其常見、但又極其容易被誤解的知識點。

最核心、最直接的結論是:在存儲和計算方面,INT(1)INT(10) 沒有任何區別。

1. 破除誤解:括號里的數字不是長度限制

很多初學者會誤以為INT(1)只能存1位數的整數,INT(10)能存10位數的整數。這是完全錯誤的

  • 存儲空間固定:在MySQL中,INT這個數據類型,無論你怎么寫,它在磁盤上占用的存儲空間永遠是固定的4個字節
  • 存儲范圍固定:因此,它的存儲范圍也永遠是固定的。對于有符號的INT,范圍是 -21474836482147483647;對于無符號的UNSIGNED INT,范圍是 04294967295
  • 結論:無論你定義的是INT(1)還是INT(10),你都可以往里面存入12345這樣的數字,只要它在INT的范圍內。
2. 唯一的區別:顯示寬度 (Display Width)

那么,括號里的這個數字到底是什么意思呢?

  • 正如您所說,它僅僅是一個 “顯示寬度” 的提示。這個概念是從早期的命令行客戶端繼承下來的,用于告訴客戶端在顯示這個字段的查詢結果時,應該預留多少個字符的寬度。
3. 唯一生效的場景:配合 ZEROFILL 使用

在現代的MySQL客戶端和各種編程語言的驅動中,這個“顯示寬度”提示基本上已經被完全忽略了。它唯一還能產生可見效果的場景,就是當這個字段同時被設置了 ZEROFILL 屬性時。

  • ZEROFILL的作用:它會自動地用前導零,來填充數字,使其達到指定的“顯示寬度”。
  • 舉例說明
    假設我們有一個字段id,類型是 INT(5) ZEROFILL
    • 如果我們存入的值是 123,那么查詢出來顯示時,就會變成 00123
    • 如果我們存入的值是 123456(超過了顯示寬度),它不會被截斷,查詢出來仍然是 123456ZEROFILL只負責補零,不負責截斷。
  • 注意:一旦為字段設置了ZEROFILL,該字段會自動變為 UNSIGNED(無符號)
4. 現代開發的最佳實踐
  • 為什么這個特性現在幾乎無用了?
    • 因為數據的展示格式,現在幾乎完全是由應用程序的后端或前端代碼來控制的,而不是依賴于數據庫的ZEROFILL這種底層特性。比如,我們需要一個5位數的訂單號,我們會在Java代碼里用String.format("%05d", orderId)來實現,而不是在數據庫里。
  • 我的實踐建議
    • 在創建表時,直接使用INTBIGINT即可,完全不需要在后面加括號和數字。比如:
      CREATE TABLE my_table (id INT,user_id BIGINT
      );
      
    • 這樣做,代碼更簡潔,也避免了給其他開發者帶來不必要的困惑。

總結一下INT(1)INT(10)在功能和存儲上完全一樣。括號里的數字是一個歷史遺留的、只在配合ZEROFILL時才生效的“顯示寬度”屬性,在現代應用開發中,我們應該直接忽略它,使用不帶括號的INT


TEXT數據類型可以無限大嗎?

面試官您好,這是一個很好的問題,也是一個常見的誤區。答案是:TEXT數據類型并不是無限大的,它有明確的長度限制。

MySQL為了滿足不同長度文本的存儲需求,提供了TEXT類型的一個“家族”,正如您所列舉的,主要有以下幾種:

類型最大長度 (字節數)約等于長度記錄開銷
TINYTEXT255 (2^8 - 1)255 B1字節
TEXT65,535 (2^16 - 1)64 KB2字節
MEDIUMTEXT16,777,215 (2^24 - 1)16 MB3字節
LONGTEXT4,294,967,295 (2^32 - 1)4 GB4字節
TEXTVARCHAR 的核心區別

在選擇存儲長文本時,我們經常會在TEXTVARCHAR之間猶豫。它們有幾個本質的區別:

  1. 行內存儲 vs. 行外存儲

    • VARCHAR:在MySQL中,VARCHAR的數據通常是存儲在數據行內部的(除非行總長度超過了限制)。
    • TEXT:為了不讓單行數據過大,TEXT類型的數據通常是存儲在行外的專用存儲空間中,而在數據行內部,只保留一個指向這塊外部空間的指針
  2. 默認值

    • VARCHAR字段可以有默認值(DEFAULT)。
    • TEXT(以及BLOB)字段不能有默認值
  3. 索引

    • VARCHAR字段可以被直接創建完整索引
    • TEXT字段因為可能非常大,不能直接創建完整索引。如果需要索引,必須指定一個前綴長度,比如INDEX(content(255)),只對內容的前255個字符創建索引。
使用TEXT類型的注意事項與最佳實踐

正是因為TEXT類型的這些底層特性,我們在使用它時需要特別注意:

  1. 性能開銷:由于數據可能存儲在行外,每次讀取TEXT字段,都可能需要一次額外的磁盤I/O(去獲取指針指向的數據),這會比直接讀取行內的VARCHAR性能要差。因此,在查詢時,應該避免不必要地SELECT *,只在確實需要時才查詢TEXT字段。

  2. 排序與分組:對TEXT字段進行ORDER BYGROUP BY操作,性能會非常低下,因為它可能需要在磁盤上創建巨大的臨時表。應盡量避免這種操作。

  3. 內存使用:如果在查詢中涉及到對TEXT字段的排序或連接,MySQL可能會在內存中分配大量的臨時空間(tmp_table_sizemax_heap_table_size),容易導致內存問題。

選型建議

  • VARCHAR優先原則:如果能夠預估出文本的最大長度,并且這個長度在MySQL的行長度限制內(通常幾千個字符內),總是優先選擇VARCHAR。比如,文章標題、用戶簡介等,用VARCHAR(255)VARCHAR(1000)就足夠了。VARCHAR的性能通常更好。
  • 什么時候用TEXT 只有當需要存儲的文本長度非常不確定,且可能非常大(超過VARCHAR的最大限制,或者幾十KB以上)時,才應該選擇TEXT類型。
    • 典型場景:用戶發表的文章正文、商品詳情的長描述、存儲的JSON或XML文檔等。
    • TEXT家族中,也應該按需選擇最小的類型。比如,如果確認內容不會超過64KB,就用TEXT,而不是MEDIUMTEXTLONGTEXT,因為更小的類型,其指針和長度記錄的開銷也更小。

總結一下TEXT不是無限大的,它是一個為了存儲超長文本而設計的“重型武器”。在使用它時,我們必須意識到它帶來的性能開銷,并遵循“按需查詢、避免排序”的最佳實踐。


IP地址如何在數據庫里存儲?

面試官您好,在數據庫中存儲IP地址,主要有兩種主流的方法:使用字符串類型(如VARCHAR使用整型(如INTBIGINT

這兩種方法各有優劣,但在追求性能和存儲效率的場景下,將IP地址轉換為整型來存儲,是更優的、也是業界推薦的最佳實踐

方案一:使用字符串 (VARCHAR) 存儲

這是最直觀、最簡單的方式。

  • 如何做:直接在表中創建一個VARCHAR(15)(對于IPv4)或VARCHAR(39)(對于IPv6)的字段來存儲點分十進制格式的IP地址字符串,如"192.168.1.1"

    CREATE TABLE access_logs (ip_address VARCHAR(15) NOT NULL,...
    );
    
  • 優點

    • 可讀性極高:在數據庫中直接查詢時,看到的就是我們熟悉的IP地址格式,非常直觀,便于調試和人工查看。
    • 實現簡單:應用程序無需任何轉換,直接將獲取到的IP字符串存入即可。
  • 缺點

    • 存儲空間占用大:存儲一個IPv4地址,即使是"1.1.1.1"(7個字符),也比整型占用的空間大。最長需要15個字節。
    • 查詢和比較效率低:基于字符串的比較、排序和范圍查詢,其效率遠低于基于整數的數值運算。比如,要查詢一個IP段,需要使用LIKE或者復雜的字符串函數,無法利用索引進行高效的范圍掃描。
方案二:使用整型 (INTBIGINT) 存儲 (推薦)

這是更專業、性能更好的方式。

  • 核心思想:IP地址本質上是一個32位(IPv4)或128位(IPv6)的無符號整數。我們可以利用數據庫的函數,將其與整數形式進行相互轉換。

  • 對于IPv4

    • 如何做:使用INT UNSIGNED(4字節無符號整型)來存儲。
      • 轉換函數:MySQL提供了兩個非常方便的函數:
        • INET_ATON('ip_address'): 將點分十進制的IP字符串,轉換為一個32位無符號整數。
        • INET_NTOA(integer_ip): 將整數形式的IP,轉換回點分十進制的字符串。
    • SQL示例
      -- 創建表
      CREATE TABLE access_logs_int (ip_address INT UNSIGNED NOT NULL,...
      );-- 插入數據
      INSERT INTO access_logs_int (ip_address) VALUES (INET_ATON('192.168.1.1'));-- 查詢數據并轉換回字符串顯示
      SELECT INET_NTOA(ip_address) FROM access_logs_int WHERE ip_address = INET_ATON('192.168.1.1');
      
  • 對于IPv6

    • 如何做:IPv6是128位的,需要使用VARBINARY(16)來存儲其二進制形式(BIGINT只有8字節,不夠用)。
    • 轉換函數:MySQL同樣提供了對應的函數:
      • INET6_ATON('ipv6_address')
      • INET6_NTOA(binary_ip)
  • 優點

    • 存儲空間小INT UNSIGNED只需要4個字節,相比VARCHAR(15),空間占用大大減少。
    • 查詢效率極高:可以非常高效地進行數值比較、排序和范圍查詢。比如,要查詢一個IP段(從start_ipend_ip),只需要用BETWEEN操作即可:
      SELECT ... FROM access_logs_int 
      WHERE ip_address BETWEEN INET_ATON('192.168.0.0') AND INET_ATON('192.168.255.255');
      
      這種查詢可以完美地利用索引,性能極佳。

總結與選型

對比維度VARCHARINT UNSIGNED
存儲空間 (IPv4)7 ~ 15 字節4 字節 (固定)
可讀性低 (需要函數轉換)
查詢/比較效率
范圍查詢復雜,低效簡單,高效 (BETWEEN)

我的選型建議

  • 對于那些對性能和存儲要求不高、且需要頻繁人工查看的少量數據場景(比如后臺管理的黑白名單配置),使用VARCHAR是簡單可行的。
  • 但對于所有大批量的、需要進行高效查詢和分析的場景,比如用戶訪問日志、安全審計日志、IP地理位置庫等,毫無疑問應該選擇使用INT UNSIGNED(或VARBINARY(16) for IPv6)來存儲。這是一種用“可讀性”換取“巨大性能和存儲優勢”的專業做法,是業界的最佳實踐。

說一下外鍵約束

面試官您好,外鍵(Foreign Key)約束是關系型數據庫中一個非常重要的概念,它的核心作用是在兩個表之間建立一種強制性的關聯關系,以保證數據的引用完整性(Referential Integrity)

1. 一個生動的比喻:訂單與顧客

我們可以用一個簡單的例子來理解它:一個訂單表 (orders)和一個顧客表 (customers)

  • 常理上,每一筆訂單都必須屬于一個真實存在的顧客。我們不希望數據庫里出現一筆“無主”的、找不到顧客的訂單。
  • 外鍵約束就是用來在數據庫層面,強制執行這個業務規則的。
2. 外鍵是如何工作的?
  1. 定義:我們會在“從表”(orders表)中創建一個字段,比如customer_id。然后,為這個customer_id字段添加一個外鍵約束,讓它引用“主表”(customers表)的主鍵id)。

    -- 主表:顧客表
    CREATE TABLE customers (id INT PRIMARY KEY,name VARCHAR(100)
    );-- 從表:訂單表
    CREATE TABLE orders (order_id INT PRIMARY KEY,order_date DATE,customer_id INT,  -- 這個字段將作為外鍵-- 定義外鍵約束FOREIGN KEY (customer_id) REFERENCES customers(id)
    );
    
  2. 強制的約束行為:一旦這個外鍵關系建立,數據庫就會像一個“嚴格的門衛”, 執行一系列規則:

    • 插入/更新時的約束 (在從表上):當你試圖在orders表中插入或更新一條記錄時,數據庫會檢查你提供的customer_id,是否在customers表的主鍵id真實存在
      • 如果存在,操作成功。
      • 如果不存在(比如你想創建一個屬于一個不存在的顧客的訂單),數據庫會拒絕這次操作,并拋出一個外鍵約束失敗的錯誤。
    • 刪除/更新時的約束 (在主表上):當你試圖從customers表中刪除一個顧客,或者修改他的id時,數據庫會檢查orders表中是否還有訂單在引用這個顧客。
      • 如果有關聯的訂單存在,那么直接的刪除或更新操作會失敗,以防止產生“孤兒訂單”。
3. 外鍵的級聯操作 (ON DELETE / ON UPDATE)

為了更靈活地處理主表記錄被刪除或更新時的情況,外鍵約束還提供了幾種級聯操作策略

  • ON DELETE CASCADE: 這是最常用的級聯刪除。當主表中的一條記錄被刪除時(比如刪除了一個顧客),所有從表中引用該記錄的行(該顧客的所有訂單)也會自動地被一并刪除
  • ON DELETE SET NULL: 當主表記錄被刪除時,從表中對應行的外鍵字段會被自動設置為NULL。這要求該外鍵字段必須允許為NULL
  • ON DELETE RESTRICT / ON DELETE NO ACTION: 這是默認的行為,即如果存在子表記錄,就禁止刪除主表記錄。

ON UPDATE也有類似的級聯操作。

4. 外鍵的優點與缺點(實踐中的權衡)
  • 優點

    1. 數據完整性的終極保障:由數據庫層面來保證數據的一致性,這是最可靠的,比完全依賴應用層的邏輯校驗要健壯得多。
    2. 清晰地表達數據關系:外鍵清晰地定義了表與表之間的依賴關系,使得數據庫結構更易于理解。
  • 缺點與爭議(為什么很多互聯網公司不用)

    1. 性能開銷:每次對從表進行INSERT/UPDATE,或者對主表進行DELETE/UPDATE,數據庫都需要進行一次額外的檢查,這在高并發的寫入場景下,會帶來一定的性能損耗
    2. 數據庫的強耦合:外鍵使得數據庫表之間產生了緊密的耦合。在進行數據庫遷移、分庫分表等架構演進時,外鍵會成為一個巨大的障礙
    3. 增加了數據庫的復雜性:級聯操作雖然方便,但也可能因為一次誤操作導致數據的意外連鎖刪除,存在一定風險。
選型結論
  • 在一些傳統的、對數據一致性要求極高、且寫入并發不那么夸張的企業級應用(如ERP、CRM) 中,使用外鍵是一個非常好的實踐。
  • 但在高并發、高流量、追求極致性能和快速迭代的互聯網應用中,很多公司會選擇放棄使用物理外鍵。他們會把數據一致性的保證,上移到應用層的業務邏輯中去實現,以換取數據庫的更高性能和更大的架構靈活性。

總結一下,外鍵是保證數據引用完整性的強大數據庫特性。但在實踐中,我們需要在 “數據的強一致性”“系統的高性能與靈活性” 之間,根據具體的業務場景和架構目標,做出一個明智的權衡。

MySQL的關鍵字IN和EXIST

面試官您好,INEXISTS是SQL中兩個非常重要的關鍵字,它們都用于子查詢中,來實現“一個表中的記錄是否存在于另一個表中”的判斷。但它們的底層執行邏輯完全不同,這也導致了它們在不同場景下的性能表現差異巨大。

我通常會用一個簡單的比喻來區分它們:

  • IN:先把“客人名單”全拿過來,再看“酒店住客”是否在名單上。
  • EXISTS:拿著“酒店住客”的名字,去“客人名單”里挨個問:“你是不是叫這個名字?”

我們用一個具體的例子來分析:

-- 查詢所有有學生的班級信息
-- 表A: classes (班級表)
-- 表B: students (學生表)
1. IN 的工作原理
  • SQL寫法
    SELECT * FROM classes WHERE id IN (SELECT class_id FROM students);
    
  • 執行邏輯
    1. 首先,執行子查詢:它會先完整地執行括號里的子查詢SELECT class_id FROM students,并將所有查詢到的class_id(比如[101, 102, 101, 103, ...]構建成一個內存中的臨時集合或哈希表。如果子查詢結果集很大,這里可能會有較大的內存開銷。
    2. 然后,執行外層查詢:接著,它會遍歷外層表classes的每一行。
    3. 進行判斷:對于classes表中的每一行,它會拿著這一行的id,去上一步構建好的那個內存集合中進行查找,判斷是否存在。
  • 核心特點先執行子查詢,再執行主查詢。 子查詢只執行一次。
2. EXISTS 的工作原理
  • SQL寫法
    SELECT * FROM classes c WHERE EXISTS (SELECT 1 FROM students s WHERE s.class_id = c.id);
    
  • 執行邏輯
    1. 首先,執行外層查詢:它會先遍歷外層表classes的第一行。
    2. 然后,執行子查詢(關聯查詢):拿到外層第一行的c.id后,它會去執行括號里的子查詢SELECT 1 FROM students s WHERE s.class_id = c.id
    3. 進行判斷
      • 子查詢的目的不是為了返回數據,而只是為了判斷 “是否存在匹配的行” 。所以我們通常寫SELECT 1SELECT *,性能沒區別。
      • 只要子查詢能找到哪怕一行匹配的數據,它就會立即停止執行,并向外層返回TRUE
      • 如果子查詢掃描了整個students表都沒有找到匹配的行,它就向外層返回FALSE
    4. 循環:接著,它會繼續取classes表的第二行,重復第2、3步,直到遍歷完整個classes表。
  • 核心特點先執行主查詢,子查詢的執行次數取決于主查詢的結果集大小。它是一種關聯子查詢(Correlated Subquery)

性能對比與選型法則

理解了它們的原理,我們就能得出一個非常經典的性能優化法則:

“小表驅動大表”

  • 當子查詢的結果集(students表中的class_id)很小時

    • 應該使用IN。因為IN會先把這個小結果集加載到內存里,外層的大表在進行匹配時,是在高效的內存集合里查找,速度很快。
    • 如果此時用EXISTS,外層的大表有多少行,子查詢就要被執行多少次,效率會很低。
  • 當外層查詢的表(classes表)很小時

    • 應該使用EXISTS。因為EXISTS會先遍歷這個小表,子查詢的執行次數就很少。
    • 并且,EXISTS的子查詢通常能利用到索引。在WHERE s.class_id = c.id這個條件上,如果students表的class_id字段有索引,那么每次子查詢都會非常快。
    • 如果此時用IN,它會先去執行那個大結果集的子查詢,可能會消耗大量時間和內存。

一個簡單好記的結論

  • 外大內小,用 IN (外層表大,子查詢結果小)
  • 外小內大,用 EXISTS (外層表小,子查詢會掃描的表大)
關于 NOT INNOT EXISTS

這個法則在NOT的場景下,結論通常是相反的,但更重要的是:

  • NOT IN有一個巨大的“陷阱”:如果子查詢的結果集中包含了任何NULL,那么NOT IN的整個查詢結果將永遠為空,這通常不是我們想要的結果。
  • NOT EXISTS則沒有這個問題,它的邏輯更嚴謹。
  • 因此,在需要進行“不存在”判斷時,強烈推薦總是使用NOT EXISTS,以避免NOT IN帶來的NULL值陷阱。

MySQL中的一些基本函數,你知道哪些?

面試官您好,MySQL提供了非常豐富的內置函數,它們極大地增強了SQL的查詢和處理能力。在我的日常開發中,我經常會使用到以下幾類函數:

1. 字符串函數 (String Functions)

這類函數用于處理和操作字符串,非常常用。

  • CONCAT(s1, s2, ...): 用于拼接多個字符串。比如,CONCAT(last_name, ', ', first_name)可以得到"Smith, John"這樣的格式。
  • LENGTH(str) / CHAR_LENGTH(str):
    • LENGTH()返回字符串的字節長度
    • CHAR_LENGTH()返回字符串的字符長度。在處理多字節字符(如UTF-8編碼的漢字)時,這個區別非常重要。
  • SUBSTRING(str, pos, len): 從字符串中截取子串
  • UPPER(str) / LOWER(str): 將字符串轉換為大寫小寫,常用于不區分大小寫的查詢匹配。
  • REPLACE(str, from_str, to_str): 替換字符串中的子串。
  • TRIM(str): 去除字符串首尾的空格
  • FIND_IN_SET(str, strlist): 在一個逗號分隔的字符串列表(strlist)中,查找str的位置。這在處理一些用逗號分隔存儲的標簽ID等場景時很有用,但通常不推薦這樣設計表結構。
  • GROUP_CONCAT(expr): 這是一個聚合函數,可以將一個分組內的多行字符串,用逗號拼接成一個單一的字符串。非常適合做一些“一對多”關系的報表展示。
2. 數值函數 (Numeric Functions)

這類函數用于進行數學運算。

  • ROUND(x, d): 對數字x進行四舍五入,保留d位小數。
  • CEIL(x) / FLOOR(x): 向上取整向下取整
  • ABS(x): 返回數字的絕對值
  • RAND(): 生成一個0到1之間的隨機數
  • MOD(n, m): 取模運算,等同于n % m
3. 日期和時間函數 (Date and Time Functions)

處理日期時間是后端開發的日常,這些函數必不可少。

  • NOW() / CURRENT_TIMESTAMP(): 獲取當前的日期和時間
  • CURDATE(): 只獲取當前日期
  • CURTIME(): 只獲取當前時間
  • DATE_FORMAT(date, format): 將日期格式化成指定的字符串。比如DATE_FORMAT(NOW(), '%Y-%m-%d')會得到"2023-10-27"
  • STR_TO_DATE(str, format): DATE_FORMAT的逆操作,將字符串解析成日期。
  • DATE_ADD(date, INTERVAL expr unit) / DATE_SUB(date, INTERVAL expr unit): 對日期進行加減運算。比如DATE_ADD(NOW(), INTERVAL 1 DAY)就是獲取明天的日期。
  • DATEDIFF(date1, date2): 計算兩個日期之間的天數差
4. 聚合函數 (Aggregate Functions)

這些函數通常與GROUP BY子句一起使用,用于進行統計計算。

  • COUNT(expr): 計算行數COUNT(*)COUNT(1)計算總行數,COUNT(column)計算該列非NULL值的行數。
  • SUM(expr): 求和。
  • AVG(expr): 求平均值。
  • MAX(expr) / MIN(expr): 求最大/最小值。
5. 控制流函數 (Control Flow Functions)

這類函數讓SQL也能實現一些簡單的邏輯判斷。

  • IF(expr1, expr2, expr3): 如果expr1為真,返回expr2,否則返回expr3。類似于Java中的三元運算符。
  • IFNULL(expr1, expr2): 如果expr1不為NULL,返回expr1,否則返回expr2。非常適合用來處理NULL值的默認顯示。
  • CASE ... WHEN ... THEN ... ELSE ... END: 實現更復雜的多條件判斷,類似于Java中的switch或多重if-else
6. 類型轉換函數 (Cast Functions)
  • CAST(expr AS type) / CONVERT(expr, type): 用于顯式地進行數據類型轉換

熟練地運用這些內置函數,可以讓我們將很多原本需要在Java代碼中處理的邏輯,下沉到數據庫層面來完成,通常能獲得更好的性能,并且讓SQL查詢本身更具表現力。


SQL查詢語句的執行順序是怎么樣的?

面試官您好,SQL查詢語句的執行順序,是一個非常重要的基礎概念。它指的是數據庫查詢引擎在邏輯上處理一個查詢的步驟順序,這個順序與我們編寫SQL語句的順序有很大的不同

理解這個邏輯執行順序,對于我們理解SQL性能優化(比如索引為什么會生效)至關重要。

1. 我們編寫的SQL順序

通常,我們編寫一個復雜的SELECT語句,其順序是這樣的:

SELECT DISTINCT ... -- (5)
FROM ... -- (1)
JOIN ... ON ...
WHERE ... -- (2)
GROUP BY ... -- (3)
HAVING ... -- (4)
ORDER BY ... -- (6)
LIMIT ... -- (7)
2. 數據庫邏輯上的執行順序

而數據庫在解析和執行這個查詢時,其邏輯上的處理流程,大致遵循以下順序:

第一步:FROMJOIN —— 確定數據源

  • 1. FROM: 首先,確定查詢的主表
  • 2. ON: 根據ON子句中的連接條件,將JOIN的表與主表進行連接,生成一個臨時的、巨大的笛卡爾積
  • 3. JOIN: 根據JOIN的類型(INNER, LEFT, RIGHT),從這個笛卡爾積中篩選出符合連接條件的行,形成一個虛擬的中間表(Virtual Table, VT1)。

第二步:WHERE —— 行級過濾

  • 4. WHERE: 對上一步生成的虛擬表VT1,逐行應用WHERE子句中的條件進行過濾。只有滿足條件的行才會被保留下來,形成第二個虛擬表(VT2)。
    • 這個階段是索引大顯身手的地方。如果WHERE條件中的字段有索引,數據庫就能高效地進行過濾,而無需全表掃描。

第三步:GROUP BY —— 分組

  • 5. GROUP BY: 如果有GROUP BY子句,數據庫會將VT2中的行,按照指定的列進行分組,形成多個組。每個組會聚合成一條記錄,形成第三個虛擬表(VT3)。

第四步:HAVING —— 組級過濾

  • 6. HAVING: 對上一步分組后的結果(VT3),應用HAVING子句中的條件進行過濾。只有滿足條件的分組才會被保留下來,形成第四個虛擬表(VT4)。
    • HAVINGWHERE的關鍵區別WHERE在分組進行過濾;HAVING在分組進行過濾。HAVING子句中可以使用聚合函數(如COUNT(*) > 5),而WHERE中不能。

第五步:SELECT —— 選取列

  • 7. SELECT: 現在,查詢引擎才真正開始處理SELECT子句。它會從上一步的結果(VT4)中,選取出我們最終需要的那些列,并可以進行計算、使用函數等,形成第五個虛擬表(VT5)。

第六步:DISTINCT —— 去重

  • 8. DISTINCT: 如果SELECT后面有DISTINCT關鍵字,引擎會對VT5中的結果進行去重,形成第六個虛擬表(VT6)。

第七步:ORDER BY —— 排序

  • 9. ORDER BY: 對上一步的結果(VT6),按照ORDER BY子句中指定的列和順序進行排序,形成第七個虛擬表(VT7)。
    • 注意ORDER BY通常在最后階段執行,所以如果排序的字段沒有索引,當結果集很大時,這個排序操作會非常耗費內存和CPU。

第八步:LIMIT / OFFSET —— 分頁

  • 10. LIMIT: 最后,如果有利LIMIT子句,引擎會從排序好的結果(VT7)中,截取出指定范圍的行,作為最終的查詢結果返回給客戶端。

總結

用一個流程圖來概括就是:
FROM/JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT

這個邏輯執行順序解釋了很多SQL現象,比如:

  • 為什么WHERE子句中不能使用SELECT中定義的別名?因為SELECTWHERE之后才執行。
  • 為什么ORDER BY可以用別名?因為它在SELECT之后執行。

理解這個順序,是編寫正確、高效SQL的基石。


SQL題:給學生表、課程成績表,求不存在01課程但存在02課程的學生的成績

前提:表結構假設

我們先假設有兩張表:

student (學生表)

s_ids_name
01趙雷
02錢電
03孫風
04李云
05周梅

score (課程成績表)

s_idc_ids_score
010180
010290
010399
020170
020260
020380
030180
030280
030380
040150
040320
050276
050387

目標:找出“周梅”這位同學,并返回他/她的成績。

解法一:使用 INNOT IN (最直觀)

這種解法最符合我們人類的思考邏輯。

  • 思路

    1. 第一步:找出所有選了'02'課程的學生的ID (s_id)。
    2. 第二步:找出所有選了'01'課程的學生的ID (s_id)。
    3. 第三步:從第一步的結果集中,篩選出那些不在第二步結果集中的學生ID。
    4. 第四步:根據篩選出的學生ID,去score表中查詢他們的所有成績。
  • SQL實現

    SELECT *
    FROM score
    WHERE s_id IN (-- 步驟3: 找出只選了02,沒選01的學生IDSELECT s_idFROM scoreWHERE c_id = '02'AND s_id NOT IN (-- 步驟2: 所有選了01課程的學生IDSELECT s_idFROM scoreWHERE c_id = '01')
    );
    
    • 注意:這種寫法在NOT IN的子查詢中,如果s_id可能為NULL,可能會產生意想不到的結果。使用NOT EXISTS通常更健壯。

解法二:使用 LEFT JOINIS NULL (性能通常更好)

這種解法通過LEFT JOIN來巧妙地實現“差集”的邏輯。

  • 思路

    1. 先找出所有選了'02'課程的學生記錄。
    2. 將這個結果集,與所有選了'01'課程的學生記錄進行左連接,連接條件是s_id相等。
    3. 如果一個選了'02'課程的學生,選了'01'課程,那么左連接一定能成功匹配上,右邊的字段將不會是NULL
    4. 反之,如果一個選了'02'課程的學生,沒有'01'課程,那么左連接會失敗,右邊的字段將全部為NULL
    5. 我們只需要篩選出那些連接后右邊字段為NULL的記錄,就找到了目標學生。
  • SQL實現

    -- 先找出目標學生ID
    SELECT s02.s_id
    FROM-- t1: 所有選了02課程的學生記錄(SELECT * FROM score WHERE c_id = '02') AS s02
    LEFT JOIN-- t2: 所有選了01課程的學生記錄(SELECT * FROM score WHERE c_id = '01') AS s01
    ON s02.s_id = s01.s_id
    WHERE-- 關鍵:篩選出那些在t2中找不到匹配的記錄s01.s_id IS NULL;-- 然后可以用這個結果作為子查詢,去score表里查成績
    SELECT *
    FROM score
    WHERE s_id IN (SELECT s02.s_idFROM (SELECT s_id FROM score WHERE c_id = '02') AS s02LEFT JOIN (SELECT s_id FROM score WHERE c_id = '01') AS s01 ON s02.s_id = s01.s_idWHERE s01.s_id IS NULL
    );
    

解法三:使用 GROUP BYHAVING (思路巧妙)

這種解法利用了分組和聚合函數來在一個查詢中完成篩選。

  • 思路

    1. 按學生ID (s_id) 進行分組。
    2. 在每個分組內,我們去統計這個學生選了'01'課程的次數和選了'02'課程的次數。
    3. 使用HAVING子句來篩選出那些 “選了'02'課程的次數大于0,并且選了'01'課程的次數等于0” 的分組。
    4. 這些分組的s_id就是我們目標學生的ID。
  • SQL實現

    SELECT s_id
    FROM score
    WHERE c_id IN ('01', '02') -- 先縮小范圍,只關心這兩門課
    GROUP BY s_id
    HAVING-- 確保選了'02'SUM(CASE WHEN c_id = '02' THEN 1 ELSE 0 END) > 0
    AND-- 確保沒選'01'SUM(CASE WHEN c_id = '01' THEN 1 ELSE 0 END) = 0;-- 同樣,可以用這個結果作為子查詢
    SELECT *
    FROM score
    WHERE s_id IN (SELECT s_idFROM scoreWHERE c_id IN ('01', '02')GROUP BY s_idHAVING SUM(CASE WHEN c_id = '02' THEN 1 ELSE 0 END) > 0AND SUM(CASE WHEN c_id = '01' THEN 1 ELSE 0 END) = 0
    );
    

總結與推薦

  • 解法一 (NOT IN):最符合直覺,但要注意NULL值陷阱,性能在子查詢結果集大時可能不佳。
  • 解法二 (LEFT JOIN):通常被認為是性能較好且邏輯嚴謹的“差集”實現方式。
  • 解法三 (GROUP BY/HAVING):思路非常巧妙,可以在一次掃描和分組中完成任務,在某些情況下性能可能最好。

在面試中,能寫出解法一說明SQL基礎合格,能寫出解法二或解法三,則更能體現您對SQL查詢優化的理解和靈活運用能力。


SQL題:給定一個學生表 student_score(stu_id,subject_id, score),查詢總分排名在5-10名的學生id及對應的總分

前提:表結構假設

student_score (學生成績表)

stu_idsubject_idscore
S01C0180
S01C0290
S02C0170
S02C0260

解法一:使用窗口函數 (Window Functions) —— 推薦的最佳實踐

這是在支持窗口函數的數據庫(如MySQL 8.0+, PostgreSQL, Oracle等)中,最簡潔、最高效、最推薦的解法。

  • 思路

    1. 使用GROUP BY子句,按stu_id分組,并用SUM(score)計算出每個學生的總分。
    2. 在第一步的結果集之上,使用DENSE_RANK()RANK()窗口函數,對總分(total_score)進行降序排名。
    3. 最后,將整個帶有排名的結果作為一個子查詢(或CTE),在外層查詢中篩選出排名在5到10之間的記錄。
  • 為什么用DENSE_RANK()RANK()

    • RANK():如果出現并列名次,會跳過之后的排名。比如,兩個人并列第2,那么下一個名次就是第4。
    • DENSE_RANK():如果出現并列名次,不會跳過之后的排名。比如,兩個人并列第2,下一個名次仍然是第3。在大多數“Top N”的場景中,DENSE_RANK()更符合業務直覺。
    • ROW_NUMBER():不考慮并列,為每一行分配一個唯一的、連續的排名。
  • SQL實現 (使用DENSE_RANK)

    -- 使用CTE (Common Table Expression) 讓查詢更清晰
    WITH StudentTotalScores AS (-- 步驟1: 計算每個學生的總分SELECTstu_id,SUM(score) AS total_scoreFROMstudent_scoreGROUP BYstu_id
    ),
    RankedScores AS (-- 步驟2: 對總分進行排名SELECTstu_id,total_score,DENSE_RANK() OVER (ORDER BY total_score DESC) AS score_rankFROMStudentTotalScores
    )
    -- 步驟3: 篩選出排名在5到10之間的學生
    SELECTstu_id,total_score
    FROMRankedScores
    WHEREscore_rank BETWEEN 5 AND 10;
    
    • 不使用CTE的寫法
    SELECT stu_id, total_score
    FROM (SELECTstu_id,total_score,DENSE_RANK() OVER (ORDER BY total_score DESC) AS score_rankFROM (SELECT stu_id, SUM(score) AS total_scoreFROM student_scoreGROUP BY stu_id) AS TotalScores
    ) AS RankedScores
    WHERE score_rank BETWEEN 5 AND 10;
    

解法二:使用 LIMITOFFSET (兼容舊版MySQL)

在不支持窗口函數的舊版MySQL中,我們可以通過先排序,再使用LIMITOFFSET來模擬這個分頁查詢。

  • 思路

    1. 先計算出每個學生的總分,并按總分降序排列。
    2. 使用LIMIT子句來獲取指定范圍的記錄。LIMIT 5, 5 或者 LIMIT 5 OFFSET 5都意味著“跳過前5條記錄,然后取接下來的5條記錄”,這恰好就是排名第6到第10。
    • 注意LIMIT的第一個參數是offset(偏移量),第二個參數是count(數量)。LIMIT 10等價于LIMIT 0, 10
  • SQL實現

    SELECTstu_id,SUM(score) AS total_score
    FROMstudent_score
    GROUP BYstu_id
    ORDER BYtotal_score DESC
    -- 跳過前4名 (第1, 2, 3, 4名),然后取6條記錄 (第5, 6, 7, 8, 9, 10名)
    LIMIT 6 OFFSET 4;
    
    • 或者更直觀的寫法LIMIT 4, 6(從第5條記錄開始,取6條)
  • 這種方法的局限性

    • 無法正確處理并列排名LIMIT只是簡單地按物理行號來截取,如果第4名和第5名是并列的,這種方法可能會錯誤地將并列第4名的某個學生排除掉。而窗口函數則能完美處理并列情況。

總結

  • 在支持窗口函數的現代數據庫中,使用 DENSE_RANK()RANK() 是解決此類排名問題的標準、最佳實踐,因為它邏輯清晰,并且能正確處理并列排名。
  • 在舊版MySQL等不支持窗口函數的環境中,可以使用 ORDER BY + LIMIT 作為一種近似的、簡化的解決方案,但必須清楚地意識到它無法處理并列排名的問題。

SQL題:查某個班級下所有學生的選課情況

前提:表結構假設

我們先假設有三張表:

students (學生信息表)

s_ids_name
S01張三
S02李四
S03王五
S04趙六

classes (學生班級表)

s_idclass_name
S01一班
S02一班
S03二班
S04一班

student_courses (學生選課表)

s_idcourse_name
S01語文
S01數學
S02語文
S03物理
S04數學
S04英語

目標:查詢“一班”所有學生的選課情況。

解法一:使用 INNER JOIN (最直接)

這是最基礎、最直接的解法,通過多級JOIN將三張表關聯起來。

  • 思路

    1. students表為基礎。
    2. students.s_idclasses.s_id連接classes表,以獲取班級信息。
    3. students.s_idstudent_courses.s_id連接student_courses表,以獲取選課信息。
    4. 最后,用WHERE子句篩選出class_name = '一班'的記錄。
  • SQL實現

    SELECTs.s_id,s.s_name,c.class_name,sc.course_name
    FROMstudents s
    INNER JOINclasses c ON s.s_id = c.s_id
    INNER JOINstudent_courses sc ON s.s_id = sc.s_id
    WHEREc.class_name = '一班';
    
  • 查詢結果

    s_ids_nameclass_namecourse_name
    S01張三一班語文
    S01張三一班數學
    S02李四一班語文
    S04趙六一班數學
    S04趙六一班英語
  • 優點:邏輯清晰,易于理解。

  • 缺點:如果某個學生沒有選任何課,那么他將不會出現在結果中。如果需求是“即使沒選課也要展示出來”,就需要用LEFT JOIN

解法二:使用 LEFT JOIN (展示所有學生,包括未選課的)

如果需要展示班級里所有學生,無論他們是否選了課,LEFT JOIN是更好的選擇。

  • 思路:與解法一類似,但將連接student_courses表的INNER JOIN改為LEFT JOIN

  • SQL實現

    SELECTs.s_id,s.s_name,c.class_name,sc.course_name -- 如果沒選課,這里會是NULL
    FROMstudents s
    INNER JOINclasses c ON s.s_id = c.s_id
    LEFT JOIN -- 使用LEFT JOINstudent_courses sc ON s.s_id = sc.s_id
    WHEREc.class_name = '一班';
    
  • 優點:能保證“一班”的所有學生都會出現在結果中,信息更完整。

解法三:使用 GROUP_CONCAT (將選課情況合并展示)

有時候,我們不希望每個學生的每門課都占一行,而是希望每個學生只占一行,他選的所有課程合并在一個字段里顯示。

  • 思路

    1. 先像解法一或解法二一樣,連接所有表并篩選出“一班”的學生。
    2. 在得到的結果集上,按學生ID和姓名進行GROUP BY分組。
    3. 使用GROUP_CONCAT()聚合函數,將每個學生分組內的所有course_name用逗號拼接起來。
  • SQL實現

    SELECTs.s_id,s.s_name,c.class_name,-- 使用GROUP_CONCAT將課程名拼接GROUP_CONCAT(sc.course_name SEPARATOR ', ') AS courses
    FROMstudents s
    INNER JOINclasses c ON s.s_id = c.s_id
    LEFT JOIN -- 這里用LEFT JOIN更好,可以處理沒選課的學生student_courses sc ON s.s_id = sc.s_id
    WHEREc.class_name = '一班'
    GROUP BYs.s_id, s.s_name, c.class_name;
    
  • 查詢結果

    s_ids_nameclass_namecourses
    S01張三一班語文, 數學
    S02李四一班語文
    S04趙六一班數學, 英語
  • 優點:結果集更緊湊,可讀性更強,非常適合在報表或前端頁面直接展示。

總結與推薦

  • INNER JOIN:適用于只需要展示有選課記錄的學生。
  • LEFT JOIN:適用于需要展示班級內所有學生,并標明其選課情況(包括未選課)的場景,是更嚴謹的做法。
  • GROUP_CONCAT:適用于需要將結果進行聚合展示,讓每個學生只占一行的場景,可讀性最好。

在面試中,能先寫出解法二(LEFT JOIN,因為它考慮得更周全,然后再根據面試官的追問,給出 解法三(GROUP_CONCAT 作為優化展示方案,會是最佳的回答策略。


如何用 MySQL 實現一個可重入的鎖?

設計方案

我會設計一個專門的 “鎖表”(distributed_locks 來記錄和管理鎖的狀態。

第一步:設計鎖表 (distributed_locks)

這張表需要包含以下幾個關鍵字段:

  • lock_name (VARCHAR): 鎖的唯一名稱。我們將使用它作為主鍵唯一索引,來保證鎖的獨占性。
  • owner_id (VARCHAR): 當前持有鎖的所有者標識。這可以是一個線程ID、一個客戶端的唯一ID、或者一個請求ID。
  • reentrant_count (INT): 重入計數器。這是實現可重入性的核心。
  • expire_time (DATETIME/TIMESTAMP): 鎖的過期時間。這是一個非常重要的“保險”機制,用于防止因客戶端崩潰而導致鎖永遠無法被釋放(死鎖)。
CREATE TABLE distributed_locks (`lock_name` VARCHAR(128) NOT NULL,`owner_id` VARCHAR(128) NOT NULL,`reentrant_count` INT NOT NULL DEFAULT 0,`expire_time` TIMESTAMP NOT NULL,PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB;
第二步:實現lock()(獲取鎖)的邏輯

獲取鎖的邏輯是最復雜的,它必須是原子的。我們不能用簡單的“先SELECTINSERT/UPDATE”的方式,因為在高并發下會有競態條件。我們會將所有邏輯封裝在一個事務中,并利用 SELECT ... FOR UPDATE這個悲觀鎖 來保證原子性。

偽代碼邏輯 (lock(lockName, ownerId, timeoutSeconds))

// 偽Java代碼
public boolean lock(String lockName, String ownerId, int timeoutSeconds) {Connection conn = null;try {conn = dataSource.getConnection();conn.setAutoCommit(false); // 開啟事務// 1. 使用 SELECT ... FOR UPDATE 悲觀地鎖定這一行(如果存在的話)// 這會阻塞其他試圖同樣鎖定這行的事務,保證了后續操作的原子性PreparedStatement ps = conn.prepareStatement("SELECT owner_id, reentrant_count, expire_time FROM distributed_locks WHERE lock_name = ? FOR UPDATE");ps.setString(1, lockName);ResultSet rs = ps.executeQuery();if (rs.next()) { // ----- 情況A:鎖記錄已存在 -----String currentOwner = rs.getString("owner_id");int count = rs.getInt("reentrant_count");Timestamp expire = rs.getTimestamp("expire_time");if (currentOwner.equals(ownerId)) {// 【可重入性體現】: 鎖的持有者是自己,直接增加重入次數PreparedStatement updatePs = conn.prepareStatement("UPDATE distributed_locks SET reentrant_count = reentrant_count + 1 WHERE lock_name = ?");updatePs.setString(1, lockName);updatePs.executeUpdate();} else {// 持有者是別人,檢查鎖是否已過期if (expire.before(new Timestamp(System.currentTimeMillis()))) {// 鎖已過期,搶占它!PreparedStatement updatePs = conn.prepareStatement("UPDATE distributed_locks SET owner_id = ?, reentrant_count = 1, expire_time = ? WHERE lock_name = ?");updatePs.setString(1, ownerId);updatePs.setTimestamp(2, new Timestamp(System.currentTimeMillis() + timeoutSeconds * 1000));updatePs.setString(3, lockName);updatePs.executeUpdate();} else {// 鎖未過期,獲取失敗conn.rollback();return false;}}} else { // ----- 情況B:鎖記錄不存在 -----// 沒有人持有鎖,直接插入新記錄來獲取鎖PreparedStatement insertPs = conn.prepareStatement("INSERT INTO distributed_locks (lock_name, owner_id, reentrant_count, expire_time) VALUES (?, ?, 1, ?)");insertPs.setString(1, lockName);insertPs.setString(2, ownerId);insertPs.setTimestamp(3, new Timestamp(System.currentTimeMillis() + timeoutSeconds * 1000));insertPs.executeUpdate();}conn.commit(); // 提交事務return true;} catch (Exception e) {if (conn != null) conn.rollback();// 異常處理return false;} finally {if (conn != null) conn.close();}
}
第三步:實現unlock()(釋放鎖)的邏輯

釋放鎖的邏輯相對簡單,但同樣需要在事務中進行。

偽代碼邏輯 (unlock(lockName, ownerId))

public boolean unlock(String lockName, String ownerId) {Connection conn = null;try {conn = dataSource.getConnection();conn.setAutoCommit(false);// 同樣,先鎖定這一行,防止并發修改PreparedStatement ps = conn.prepareStatement("SELECT owner_id, reentrant_count FROM distributed_locks WHERE lock_name = ? FOR UPDATE");ps.setString(1, lockName);ResultSet rs = ps.executeQuery();if (rs.next()) {String currentOwner = rs.getString("owner_id");if (!currentOwner.equals(ownerId)) {// 如果鎖的持有者不是自己,無權釋放,這可能是一個嚴重的邏輯錯誤conn.rollback();throw new IllegalMonitorStateException("Attempt to unlock a lock not owned by the current thread/client.");}int count = rs.getInt("reentrant_count");if (count > 1) {// 【可重入性體現】: 只是減少重入次數,并不真正釋放鎖PreparedStatement updatePs = conn.prepareStatement("UPDATE distributed_locks SET reentrant_count = reentrant_count - 1 WHERE lock_name = ?");updatePs.setString(1, lockName);updatePs.executeUpdate();} else {// 重入次數為1,這是最后一次釋放,直接刪除鎖記錄PreparedStatement deletePs = conn.prepareStatement("DELETE FROM distributed_locks WHERE lock_name = ?");deletePs.setString(1, lockName);deletePs.executeUpdate();}} else {// 鎖記錄本就不存在,可能也是一個邏輯錯誤conn.rollback();// log a warning or do nothingreturn true;}conn.commit();return true;} catch (Exception e) {if (conn != null) conn.rollback();return false;} finally {if (conn != null) conn.close();}
}

總結

這個設計的核心在于:

  1. 利用數據庫主鍵或唯一索引的唯一性,來保證鎖的獨占性
  2. 利用SELECT ... FOR UPDATE悲觀鎖和事務,來保證“檢查-再操作”這個過程的原子性
  3. 引入一個reentrant_count計數器,在lock()unlock()時進行增減,來實現可重入性
  4. 引入一個expire_time過期時間,作為兜底機制,防止因持有者宕機而導致的永久死鎖

通過這套設計,我們就能在MySQL層面,模擬出一個功能相對完備的、可重入的分布式鎖。當然,在生產環境中,我們通常會優先選擇像Redis的RedLockZooKeeper這樣更專業的分布式鎖實現。

參考小林coding

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86848.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86848.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86848.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

華為OD機考-數字螺旋矩陣(JAVA 2025B卷)

public class RotateMatrix {public static void main(String[] args) {// 順時針螺旋矩陣printMatrixV1();// 逆時針螺旋矩陣//printMatrixV2();}private static void printMatrixV2() {Scanner scan new Scanner(System.in);while(scan.hasNextLine()){String[] line scan.…

【Java工程師面試全攻略】Day7:分布式系統設計面試精要

一、分布式系統概述 分布式系統已成為現代互聯網應用的標配架構,據LinkedIn統計,分布式系統設計能力是高級Java工程師薪資差異的關鍵因素。今天我們將深入解析分布式系統的核心理論和實踐,幫助你掌握面試中的系統設計問題。 二、分布式理論…

Excel處理控件Aspose.Cells教程:在Excel 文件中創建、操作和渲染時間線

您可以使用數據透視表時間軸,而無需調整過濾器來顯示日期——這是一種動態過濾器選項,可讓您輕松按日期/時間進行過濾,并使用滑塊控件放大所需的時間段。Microsoft Excel 允許您通過選擇數據透視表,然后單擊“插入”>“時間軸”…

Python----神經網絡發(神經網絡發展歷程)

年份網絡名稱突出點主要成就論文地址1989LeNet首個現代卷積神經網絡(CNN),引入卷積、池化操作手寫數字識別先驅,奠定CNN基礎MNIST Demos on Yann LeCuns website2012AlexNet首次大規模使用深度卷積神經網絡進行圖像識別&#xff1…

mvc與mvp

mvc MVC 架構中,Activity/Fragment(作為 View 和 Controller)直接持有 Model 或異步任務的引用,當頁面銷毀時,這些長生命周期對象若未正確釋放,會導致 Activity 無法被 GC 回收,形成內存泄漏。…

商業智能中的地圖可視化模板:助力數據高效呈現

引言 在數字化浪潮席卷的當下,數據可視化的重要性愈發凸顯。企業和組織需要從海量的數據中提取有價值的信息,以便做出明智的決策。而可視化地圖組件作為數據可視化的關鍵部分,能夠將數據與地理位置相結合,以直觀、美觀的方式展示…

Opencv 相機標定相關API及原理介紹

Opencv 相機標定相關API及原理介紹 相機標定是計算機視覺中的基礎任務,旨在確定相機的??內參矩陣??、??畸變系數??以及(可選)??外參??(相機相對于世界坐標系的旋轉和平移)。OpenCV提供了完整的相機標定工具鏈,核心函數為cv2.calibrateCamera,其原理基于張正…

深入剖析AI大模型:Prompt 從理論框架到復雜任務的全場景實現

今天我們就Prompt實戰,實現一下復雜場景,通過這些實戰我們就可以更好的理解大模型工作的原理和機制了。我個人覺得Prompt是AI大模型中非常重要的的環節。首先我們還是溫習一下Prompt的框架和基礎原則。然后我們就文本生成、問答任務及復雜任務三個方面分…

Fractal Generative Models論文閱讀筆記與代碼分析

何愷明分型模型這篇文章在二月底上傳到arXiv預出版網站到現在已經過了三個月,當時我也聽說這篇文章時感覺是大有可為,但是幾個月不知道忙啥了,可能錯過很多機會,但是亡羊補牢嘛,而且截至目前,該文章應該也還…

IntelliJ IDEA代碼提示忽略大小寫設置詳解

目錄 前言一、設置步驟1. 打開設置界面2. 進入代碼補全設置3. 配置大小寫敏感選項新版本(2023及以上)舊版本(2022及以下) 4. 保存并應用設置 二、效果驗證示例三、注意事項與常見問題1. **適用范圍**2. **版本兼容性**3. **設置未…

Oracle集群OCR磁盤組掉盤問題處理

問題描述 填寫問題的基礎信息。 系統名稱 - IP地址 - 操作系統 HP-UNIX 數據庫 Oracle 11.2.0.4 兩節點RAC 癥狀表現 問題的癥狀表現如下 集群的OCR磁盤組掉了一塊盤(/dev/rdisk/disk52): 查詢集群仲裁盤發現只有兩塊(原來是有三塊)&#xff…

在WordPress中徹底關閉生成縮略圖的方法

在WordPress中徹底關閉生成縮略圖有多種方法,以下是幾種常見的方法: 方法一:通過修改主題的functions.php文件 登錄WordPress后臺:進入WordPress后臺管理界面。 編輯主題文件: 在左側菜單中找到“外觀”選項&#…

安全-Linux基線核查項點

Linux基線加固/整改 1.限制超級管理員遠程登錄 修改遠程管理程序ssh的配置文件 vi /etc/ssh/sshd_config PermitRootLogin no 重啟sshd服務 systemctl restart sshd 2. 修改默認密碼生存周期 一個好的密碼時間策略如下: vi /etc/login.defs PASS_MAX_DAY 90 最長…

在微信小程序中使用骨架屏

在微信小程序中使用骨架屏可以優化用戶體驗,避免頁面加載時出現白屏現象。以下是詳細的使用方法和注意事項: 使用方法 生成骨架屏代碼: 打開微信開發者工具,進入需要添加骨架屏的頁面。在模擬器面板右下角點擊三個點&#xff0c…

網絡的那些事——初級——OSPF(1)

💎什么是OSPF? OSPF(Open Shortest Path First,開放最短路徑優先)是一種基于鏈路狀態的內部網關協議(IGP),廣泛應用于中大型企業及運營商網絡。其核心設計目標是解決早期協議(如RI…

前端導出PDF(適配ios Safari瀏覽器)

目前市面上常用的前端導出PDF庫組合一般為: 1. html2canvas js-pdf 2. html2canvaspdf-lib 3. domtoimagepdf-lib 因本人項目中導出pdf需求為導出30頁及以上的多頁pdf,考慮性能問題,選擇了 html2canvaspdf-lib 及domtoimagepdf-lib兩種方…

physicsnemo開源程序是開源深度學習框架,用于使用最先進的 Physics-ML 方法構建、訓練和微調深度學習模型

?一、軟件介紹 文末提供程序和源碼下載 NVIDIA PhysicsNeMo 是一個開源深度學習框架,用于使用最先進的 SciML 方法構建、訓練、微調和推理物理 AI 模型,以實現 AI4 科學和工程。PhysicsNeMo 提供 python 模塊來構建可擴展和優化的訓練和推理管道&#…

JDBC接口開發指南

1.簡介 JDBC(Java Data Base Connectivity,java數據庫連接)是一種用于執行SQL語句的Java API,可以為多種關系數據庫提供統一訪問,它由一組用Java語言編寫的類和接口組成。JDBC提供了一種基準,據此可以構建更高級的工具…

Shell 腳本:系統管理與任務自動化的利器

在開發者忙碌的日常工作中,效率就是生命線。當面對大量重復、繁瑣的系統管理任務與開發流程時,一款得力的編程工具能讓工作事半功倍。Shell 腳本,這把在 Linux 和 Unix 系統環境下閃耀著光芒的利器,憑借其強大的自動化能力&#x…

關于mybatis插入大批量數據效率問題

一、即便分批次用mybatis插入數據,效率依舊不高,原因: MyBatis一次性批量插入幾千條數據,為什么性能很差?-騰訊云開發者社區-騰訊云 文中提出: 默認執行器類型為Simple,會為每個語句創建一個新…