最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MySQL存粹問題面試準(zhǔn)備總結(jié)大全

 更新時(shí)間:2026年05月03日 08:35:04   作者:Nontee  
這篇文章主要介紹了MySQL存粹問題面試準(zhǔn)備總結(jié)的相關(guān)資料,MySQL面試中常見問題,包括但不限于事務(wù),索引,引擎,場(chǎng)景優(yōu)化等常見問題,有助于面試前的準(zhǔn)備,需要的朋友可以參考下

一、MySQL基礎(chǔ)篇

面試官:請(qǐng)你介紹一下MySQL的架構(gòu),它主要由哪些部分組成?

回答:

好的,MySQL的架構(gòu)可以分為三層,我從上到下給您介紹一下:

第一層是連接層,也叫客戶端連接層。當(dāng)我們的應(yīng)用程序連接MySQL時(shí),首先會(huì)經(jīng)過這一層。它主要負(fù)責(zé):

  • 處理客戶端的連接請(qǐng)求
  • 進(jìn)行身份認(rèn)證,驗(yàn)證用戶名密碼
  • 管理連接池,維護(hù)線程

第二層是服務(wù)層,這是MySQL的核心層,包含了很多重要組件:

  • 查詢緩存:不過在MySQL 8.0已經(jīng)移除了,因?yàn)槊新侍?/li>
  • 解析器:對(duì)SQL語句進(jìn)行詞法分析和語法分析,生成解析樹
  • 優(yōu)化器:這個(gè)很重要,它會(huì)對(duì)SQL進(jìn)行優(yōu)化,選擇最優(yōu)的執(zhí)行計(jì)劃,比如選擇用哪個(gè)索引
  • 執(zhí)行器:調(diào)用存儲(chǔ)引擎接口,執(zhí)行SQL語句

第三層是存儲(chǔ)引擎層,MySQL支持插件式的存儲(chǔ)引擎,常見的有InnoDB、MyISAM等。存儲(chǔ)引擎負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)和讀取。

一條SQL語句的執(zhí)行流程大概是這樣的:客戶端發(fā)送SQL → 連接器驗(yàn)證 → 解析器解析 → 優(yōu)化器優(yōu)化 → 執(zhí)行器執(zhí)行 → 存儲(chǔ)引擎讀寫數(shù)據(jù)。

面試官:你剛才提到了InnoDB和MyISAM,能詳細(xì)說說它們的區(qū)別嗎?

回答:

好的,這兩個(gè)是MySQL最常用的存儲(chǔ)引擎,它們的區(qū)別我從幾個(gè)維度來說:

首先是事務(wù)支持方面:

  • InnoDB支持事務(wù),支持ACID特性,可以進(jìn)行COMMIT和ROLLBACK操作
  • MyISAM不支持事務(wù),如果操作出錯(cuò),沒辦法回滾

其次是鎖的粒度:

  • InnoDB支持行級(jí)鎖,鎖的粒度更細(xì),并發(fā)性能更好
  • MyISAM只支持表級(jí)鎖,一個(gè)寫操作會(huì)鎖住整張表,并發(fā)性能較差

第三是外鍵約束:

  • InnoDB支持外鍵,可以保證數(shù)據(jù)的引用完整性
  • MyISAM不支持外鍵

第四是索引結(jié)構(gòu):

  • InnoDB使用聚簇索引,數(shù)據(jù)文件和索引文件是綁定在一起的,主鍵索引的葉子節(jié)點(diǎn)直接存儲(chǔ)數(shù)據(jù)行
  • MyISAM使用非聚簇索引,數(shù)據(jù)文件和索引文件是分開的,索引的葉子節(jié)點(diǎn)存儲(chǔ)的是數(shù)據(jù)的地址

第五是COUNT(*)性能:

  • MyISAM會(huì)保存表的總行數(shù),執(zhí)行COUNT(*)時(shí)直接返回,非常快
  • InnoDB不保存行數(shù),需要全表掃描來統(tǒng)計(jì)

第六是崩潰恢復(fù):

  • InnoDB有redo log和undo log,支持崩潰后的數(shù)據(jù)恢復(fù)
  • MyISAM沒有這個(gè)機(jī)制,崩潰后可能丟失數(shù)據(jù)

總結(jié)一下,現(xiàn)在基本都推薦使用InnoDB,它從MySQL 5.5版本開始就是默認(rèn)引擎了。除非是一些只讀的、不需要事務(wù)的場(chǎng)景,否則都應(yīng)該選InnoDB。

面試官:你提到InnoDB使用聚簇索引,能詳細(xì)解釋一下什么是聚簇索引和非聚簇索引嗎?

回答:

好的,這個(gè)問題很重要,我來詳細(xì)說一下。

聚簇索引,英文叫Clustered Index,它的特點(diǎn)是:索引和數(shù)據(jù)是存儲(chǔ)在一起的。在InnoDB中,主鍵索引就是聚簇索引,它的B+Tree葉子節(jié)點(diǎn)直接存儲(chǔ)的是完整的數(shù)據(jù)行。

舉個(gè)形象的例子,聚簇索引就像一本按拼音順序排列的字典,字的解釋就直接跟在拼音后面,找到拼音就找到了內(nèi)容。

非聚簇索引,也叫二級(jí)索引或輔助索引,它的葉子節(jié)點(diǎn)存儲(chǔ)的不是數(shù)據(jù)行,而是主鍵的值。

比如我們?cè)趗sername字段上建了一個(gè)普通索引,當(dāng)我們通過username查詢時(shí):

  • 首先在username索引的B+Tree中找到對(duì)應(yīng)的主鍵id
  • 然后再用這個(gè)id去聚簇索引中查找完整的數(shù)據(jù)行

這個(gè)過程就叫做回表。

關(guān)于回表,我再補(bǔ)充一下: 回表是有性能損耗的,因?yàn)樾枰獌纱蜝+Tree查找。所以在實(shí)際開發(fā)中,我們會(huì)盡量避免回表,方法就是使用覆蓋索引。

什么是覆蓋索引呢?就是我們查詢的字段剛好都在索引中,不需要回表就能拿到數(shù)據(jù)。比如:

-- 假設(shè)有聯(lián)合索引 idx_name_age(name, age)
SELECT name, age FROM user WHERE name = '張三';
-- 這個(gè)查詢就用到了覆蓋索引,不需要回表

還有一點(diǎn)要補(bǔ)充:InnoDB要求表必須有主鍵。如果我們沒有顯式定義主鍵,InnoDB會(huì)這樣處理:

  • 首先找一個(gè)非空的唯一索引作為聚簇索引
  • 如果也沒有,就會(huì)自動(dòng)生成一個(gè)6字節(jié)的隱藏主鍵ROW_ID

所以建表時(shí)一定要顯式定義主鍵,而且推薦使用自增主鍵,因?yàn)轫樞虿迦胄矢?,不?huì)造成頁分裂。

二、索引篇

面試官:為什么MySQL選擇B+Tree作為索引的數(shù)據(jù)結(jié)構(gòu),而不是B-Tree或者Hash?

回答:

這是個(gè)好問題,我從幾個(gè)角度來分析:

首先說說為什么不用Hash:

Hash索引的等值查詢確實(shí)很快,時(shí)間復(fù)雜度是O(1)。但它有幾個(gè)致命缺點(diǎn):

  • 不支持范圍查詢:Hash只能做等值比較,像WHERE age > 20這種范圍查詢就沒法用
  • 不支持排序:Hash是散列存儲(chǔ)的,沒有順序
  • 不支持最左前綴匹配:對(duì)于聯(lián)合索引沒辦法部分使用
  • 存在Hash沖突:大量數(shù)據(jù)時(shí)沖突會(huì)影響性能

然后說說B+Tree相比B-Tree的優(yōu)勢(shì):

B-Tree的每個(gè)節(jié)點(diǎn)都存儲(chǔ)數(shù)據(jù),而B+Tree只在葉子節(jié)點(diǎn)存儲(chǔ)數(shù)據(jù),非葉子節(jié)點(diǎn)只存儲(chǔ)索引鍵。這帶來幾個(gè)好處:

  • 樹的高度更低

    • 因?yàn)榉侨~子節(jié)點(diǎn)不存數(shù)據(jù),每個(gè)節(jié)點(diǎn)能存儲(chǔ)更多的索引鍵
    • 一個(gè)節(jié)點(diǎn)通常是一個(gè)磁盤頁(16KB),能存更多key意味著樹更矮
    • 樹矮了,磁盤IO次數(shù)就少了,查詢更快
  • 范圍查詢效率高

    • B+Tree的葉子節(jié)點(diǎn)之間用雙向鏈表連接
    • 范圍查詢時(shí),找到起點(diǎn)后沿著鏈表遍歷就行
    • B-Tree做范圍查詢需要中序遍歷,效率低很多
  • 查詢效率穩(wěn)定

    • B+Tree所有查詢都要走到葉子節(jié)點(diǎn),路徑長(zhǎng)度一致
    • B-Tree可能在中間節(jié)點(diǎn)就找到數(shù)據(jù),查詢效率不穩(wěn)定

實(shí)際舉個(gè)例子:

假設(shè)一個(gè)節(jié)點(diǎn)能存1000個(gè)key,3層的B+Tree能存儲(chǔ)1000×1000×1000 = 10億條數(shù)據(jù),而3次磁盤IO就能定位到任意數(shù)據(jù)。這就是B+Tree的威力。

面試官:什么是最左前綴原則?能舉例說明嗎?

回答:

好的,最左前綴原則是使用聯(lián)合索引時(shí)必須遵循的規(guī)則。

簡(jiǎn)單來說,對(duì)于聯(lián)合索引,查詢條件必須從索引的最左邊開始匹配,并且不能跳過中間的列。

我舉個(gè)具體例子:

假設(shè)我們有一個(gè)聯(lián)合索引idx_a_b_c(a, b, c),包含a、b、c三個(gè)字段。

-- 能用到索引的情況:
WHERE a = 1                    -- 用到索引(a)
WHERE a = 1 AND b = 2          -- 用到索引(a, b)
WHERE a = 1 AND b = 2 AND c = 3  -- 用到索引(a, b, c),完全匹配

-- 能部分用到索引的情況:
WHERE a = 1 AND c = 3          -- 只用到(a),c用不上,因?yàn)樘^了b

-- 用不到索引的情況:
WHERE b = 2                    -- 用不到,沒有最左邊的a
WHERE b = 2 AND c = 3          -- 用不到,沒有最左邊的a
WHERE c = 3                    -- 用不到

有一個(gè)特殊情況要注意:查詢條件的順序不影響索引使用,因?yàn)镸ySQL優(yōu)化器會(huì)自動(dòng)調(diào)整順序。

WHERE b = 2 AND a = 1          -- 優(yōu)化器會(huì)調(diào)整為 a = 1 AND b = 2,能用到索引

還有一個(gè)重要的點(diǎn):范圍查詢會(huì)導(dǎo)致后面的列無法使用索引。

WHERE a = 1 AND b > 5 AND c = 3  
-- a走索引,b走索引(范圍),但c就用不到索引了

實(shí)際工作中的建議:

  • 把區(qū)分度高的字段放在聯(lián)合索引的前面
  • 把等值查詢的字段放在范圍查詢字段的前面
  • 利用覆蓋索引避免回表

面試官:哪些情況會(huì)導(dǎo)致索引失效?

回答:

索引失效是面試高頻題,我總結(jié)了幾種常見的情況:

1. 違反最左前綴原則

-- 聯(lián)合索引 idx_a_b_c(a, b, c)
SELECT * FROM t WHERE b = 1;  -- 沒有a,索引失效

2. 在索引列上使用函數(shù)或運(yùn)算

-- 索引失效
SELECT * FROM user WHERE LEFT(name, 3) = '張';
SELECT * FROM user WHERE age + 1 = 25;
SELECT * FROM user WHERE YEAR(create_time) = 2024;
-- 正確寫法
SELECT * FROM user WHERE name LIKE '張%';
SELECT * FROM user WHERE age = 24;
SELECT * FROM user WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';

3. 隱式類型轉(zhuǎn)換

-- 假設(shè)phone是varchar類型
SELECT * FROM user WHERE phone = 13800138000;  -- 數(shù)字,索引失效
SELECT * FROM user WHERE phone = '13800138000';  -- 字符串,索引有效

這里的原理是:當(dāng)類型不匹配時(shí),MySQL會(huì)對(duì)索引列進(jìn)行類型轉(zhuǎn)換,相當(dāng)于加了函數(shù),就失效了。

4. LIKE以%開頭

SELECT * FROM user WHERE name LIKE '%張';   -- 索引失效
SELECT * FROM user WHERE name LIKE '%張%';  -- 索引失效
SELECT * FROM user WHERE name LIKE '張%';   -- 索引有效

5. OR條件中有非索引列

-- 假設(shè)name有索引,status沒有索引
SELECT * FROM user WHERE name = '張三' OR status = 1;  -- 整體不走索引

要解決這個(gè)問題,要么給status也加索引,要么改寫成UNION:

SELECT * FROM user WHERE name = '張三'
UNION
SELECT * FROM user WHERE status = 1;

6. 使用NOT IN、NOT EXISTS、!=、<>

SELECT * FROM user WHERE id NOT IN (1, 2, 3);
SELECT * FROM user WHERE name != '張三';

這些操作可能導(dǎo)致優(yōu)化器認(rèn)為全表掃描更快,從而放棄索引。

7. IS NULL / IS NOT NULL(看情況)

如果表中NULL值很多,IS NOT NULL可能走索引;如果NULL值很少,IS NULL可能走索引。這個(gè)取決于數(shù)據(jù)分布和優(yōu)化器的判斷。

總結(jié)一句話:索引列要保持"干凈",不要對(duì)它做任何加工,讓它直接參與比較。

三、事務(wù)篇

面試官:說說事務(wù)的四大特性ACID,以及MySQL是如何實(shí)現(xiàn)的?

回答:

ACID是事務(wù)的四大特性,我來逐一說明:

A - 原子性(Atomicity)

原子性是指事務(wù)是一個(gè)不可分割的整體,要么全部成功,要么全部失敗。

比如轉(zhuǎn)賬操作,A給B轉(zhuǎn)100塊:

  • A的賬戶減100
  • B的賬戶加100

這兩步必須同時(shí)成功或同時(shí)失敗,不能出現(xiàn)A扣了錢但B沒收到的情況。

MySQL的實(shí)現(xiàn)方式:通過undo log(回滾日志) 來實(shí)現(xiàn)。在執(zhí)行事務(wù)時(shí),MySQL會(huì)把修改前的數(shù)據(jù)保存到undo log中。如果事務(wù)需要回滾,就用undo log中的數(shù)據(jù)恢復(fù)。

C - 一致性(Consistency)

一致性是指事務(wù)執(zhí)行前后,數(shù)據(jù)庫從一個(gè)一致狀態(tài)變到另一個(gè)一致狀態(tài)。比如轉(zhuǎn)賬前后,A和B的總金額應(yīng)該不變。

MySQL的實(shí)現(xiàn)方式:一致性是事務(wù)的最終目標(biāo),是由其他三個(gè)特性(原子性、隔離性、持久性)共同保證的。

I - 隔離性(Isolation)

隔離性是指多個(gè)事務(wù)并發(fā)執(zhí)行時(shí),相互之間不能干擾。一個(gè)事務(wù)內(nèi)部的操作對(duì)其他并發(fā)事務(wù)是隔離的。

MySQL的實(shí)現(xiàn)方式:通過MVCC(多版本并發(fā)控制) 來實(shí)現(xiàn)。

  • 寫寫沖突通過鎖來解決
  • 讀寫沖突通過MVCC來解決

D - 持久性(Durability)

持久性是指事務(wù)一旦提交,對(duì)數(shù)據(jù)的修改就是永久的,即使系統(tǒng)崩潰也不會(huì)丟失。

MySQL的實(shí)現(xiàn)方式:通過redo log(重做日志) 來實(shí)現(xiàn)。事務(wù)提交時(shí),先把修改寫入redo log并刷盤,這樣即使系統(tǒng)崩潰,重啟后也能通過redo log恢復(fù)數(shù)據(jù)。

補(bǔ)充一下redo log和undo log的區(qū)別:

  • redo log:記錄的是"物理日志",即數(shù)據(jù)頁的修改,用于崩潰恢復(fù)
  • undo log:記錄的是"邏輯日志",即相反的操作,用于事務(wù)回滾和MVCC

面試官:MySQL的事務(wù)隔離級(jí)別有哪些?分別能解決什么問題?

回答:

MySQL有四種事務(wù)隔離級(jí)別,從低到高分別是:

1. 讀未提交(READ UNCOMMITTED)

這是最低的隔離級(jí)別,一個(gè)事務(wù)可以讀取到另一個(gè)事務(wù)未提交的數(shù)據(jù)。

問題:會(huì)產(chǎn)生臟讀。比如事務(wù)A讀到了事務(wù)B修改但未提交的數(shù)據(jù),如果B回滾了,A讀到的就是無效的臟數(shù)據(jù)。

實(shí)際開發(fā)中基本不用這個(gè)級(jí)別。

2. 讀已提交(READ COMMITTED)

一個(gè)事務(wù)只能讀取到其他事務(wù)已提交的數(shù)據(jù)。

解決了:臟讀問題
仍存在:不可重復(fù)讀。就是在同一個(gè)事務(wù)中,兩次讀取同一條數(shù)據(jù)可能得到不同的結(jié)果,因?yàn)槠陂g可能有其他事務(wù)修改并提交了。

Oracle數(shù)據(jù)庫的默認(rèn)隔離級(jí)別就是這個(gè)。

3. 可重復(fù)讀(REPEATABLE READ)

在同一個(gè)事務(wù)中多次讀取同一數(shù)據(jù),結(jié)果是一致的。

解決了:臟讀、不可重復(fù)讀
仍存在:幻讀。就是在同一事務(wù)中,兩次查詢的結(jié)果集行數(shù)不同,因?yàn)橛衅渌聞?wù)插入或刪除了數(shù)據(jù)。

但是,MySQL的InnoDB在這個(gè)級(jí)別通過Next-Key Lock(臨鍵鎖) 在很大程度上解決了幻讀問題。

MySQL的默認(rèn)隔離級(jí)別就是REPEATABLE READ。

4. 串行化(SERIALIZABLE)

最高的隔離級(jí)別,事務(wù)串行執(zhí)行,完全隔離。

解決了:臟讀、不可重復(fù)讀、幻讀
代價(jià):性能最差,并發(fā)度最低

我用表格總結(jié)一下:

隔離級(jí)別臟讀不可重復(fù)讀幻讀
READ UNCOMMITTED可能可能可能
READ COMMITTED不會(huì)可能可能
REPEATABLE READ不會(huì)不會(huì)可能(InnoDB基本解決)
SERIALIZABLE不會(huì)不會(huì)不會(huì)

實(shí)際工作中的選擇:

  • 大多數(shù)互聯(lián)網(wǎng)項(xiàng)目用默認(rèn)的REPEATABLE READ就夠了
  • 對(duì)于一些金融類的高一致性要求場(chǎng)景,可能會(huì)用到SERIALIZABLE
  • 如果是讀多寫少、對(duì)一致性要求不那么高的場(chǎng)景,可以用READ COMMITTED提高并發(fā)性能

面試官:什么是MVCC?它是怎么實(shí)現(xiàn)的?

回答:

MVCC,全稱Multi-Version Concurrency Control,多版本并發(fā)控制。它的核心思想是:通過保存數(shù)據(jù)的多個(gè)版本,讓讀操作和寫操作不沖突,從而提高并發(fā)性能。

簡(jiǎn)單來說,當(dāng)你讀數(shù)據(jù)的時(shí)候,讀的是某一個(gè)歷史版本;當(dāng)其他事務(wù)在寫數(shù)據(jù)的時(shí)候,并不影響你的讀取。這就是所謂的"快照讀"。

MVCC的實(shí)現(xiàn)依賴三個(gè)核心組件:

1. 隱藏字段

InnoDB會(huì)為每行數(shù)據(jù)添加幾個(gè)隱藏字段:

  • DB_TRX_ID(6字節(jié)):記錄最后一次修改該行的事務(wù)ID
  • DB_ROLL_PTR(7字節(jié)):回滾指針,指向這行數(shù)據(jù)的上一個(gè)版本(在undo log中)
  • DB_ROW_ID(6字節(jié)):隱藏的自增主鍵,如果表沒有主鍵才會(huì)有這個(gè)

2. Undo Log(版本鏈)

每次修改數(shù)據(jù)時(shí),舊版本會(huì)被保存到undo log中。多次修改就形成了一個(gè)版本鏈:

當(dāng)前數(shù)據(jù) → undo log版本1 → undo log版本2 → undo log版本3 → ...

通過DB_ROLL_PTR指針可以找到所有歷史版本。

3. Read View(讀視圖)

當(dāng)事務(wù)執(zhí)行快照讀(普通SELECT)時(shí),會(huì)生成一個(gè)Read View,它包含:

  • m_ids:當(dāng)前活躍(未提交)的事務(wù)ID列表
  • min_trx_id:m_ids中的最小事務(wù)ID
  • max_trx_id:系統(tǒng)應(yīng)該分配給下一個(gè)事務(wù)的ID
  • creator_trx_id:創(chuàng)建這個(gè)Read View的事務(wù)ID

Read View的可見性判斷規(guī)則:

對(duì)于版本鏈中的某個(gè)版本,它的trx_id與Read View比較:

  • 如果trx_id < min_trx_id,說明這個(gè)版本在Read View創(chuàng)建之前就已經(jīng)提交了,可見
  • 如果trx_id >= max_trx_id,說明這個(gè)版本是在Read View創(chuàng)建之后才生成的,不可見
  • 如果min_trx_id <= trx_id < max_trx_id:
    • 如果trx_id在m_ids中,說明這個(gè)版本的事務(wù)還未提交,不可見
    • 如果trx_id不在m_ids中,說明這個(gè)版本的事務(wù)已經(jīng)提交,可見

不同隔離級(jí)別下Read View的生成時(shí)機(jī)不同:

  • READ COMMITTED:每次SELECT都會(huì)生成新的Read View
  • REPEATABLE READ:只在第一次SELECT時(shí)生成Read View,后續(xù)復(fù)用

這就是為什么RC級(jí)別會(huì)有不可重復(fù)讀,而RR級(jí)別可以保證可重復(fù)讀的原因。

四、鎖篇

面試官:MySQL中有哪些類型的鎖?

回答:

MySQL的鎖可以從多個(gè)維度來分類,我來詳細(xì)說一下:

一、按鎖的粒度分

1. 全局鎖鎖住整個(gè)數(shù)據(jù)庫實(shí)例,使其處于只讀狀態(tài)。主要用于全庫邏輯備份。

FLUSH TABLES WITH READ LOCK;  -- 加鎖
UNLOCK TABLES;                -- 解鎖

2. 表級(jí)鎖鎖住整張表,開銷小、加鎖快,但并發(fā)度低。包括:

  • 表鎖:LOCK TABLES t READ/WRITE
  • 元數(shù)據(jù)鎖(MDL):訪問表時(shí)自動(dòng)加,防止DDL和DML沖突
  • 意向鎖:表級(jí)別的鎖,用于快速判斷表中是否有行鎖

3. 行級(jí)鎖只鎖住需要的行,開銷大、加鎖慢,但并發(fā)度高。InnoDB支持行級(jí)鎖。

二、按鎖的模式分

1. 共享鎖(S鎖/讀鎖)

SELECT ... LOCK IN SHARE MODE;  -- MySQL 5.x
SELECT ... FOR SHARE;           -- MySQL 8.0+

多個(gè)事務(wù)可以同時(shí)持有S鎖,用于讀取數(shù)據(jù)。

2. 排他鎖(X鎖/寫鎖)

SELECT ... FOR UPDATE;

只有一個(gè)事務(wù)能持有X鎖,用于修改數(shù)據(jù)。INSERT/UPDATE/DELETE會(huì)自動(dòng)加X鎖。

三、InnoDB的行鎖類型(重點(diǎn))

1. Record Lock(記錄鎖)鎖住索引中的一條記錄。

2. Gap Lock(間隙鎖)鎖住索引記錄之間的間隙,防止其他事務(wù)在間隙中插入數(shù)據(jù)。這是為了解決幻讀問題。

比如表中有id為1、5、10的記錄,間隙鎖可以鎖住(1,5)、(5,10)這些區(qū)間。

3. Next-Key Lock(臨鍵鎖)Record Lock + Gap Lock的組合,鎖住一條記錄以及它前面的間隙。

這是InnoDB在REPEATABLE READ級(jí)別下默認(rèn)的行鎖算法。

舉個(gè)例子:假設(shè)表中有id為1、5、10的記錄,執(zhí)行:

SELECT * FROM t WHERE id = 5 FOR UPDATE;

在RR級(jí)別下,會(huì)加Next-Key Lock,鎖住(1,5]這個(gè)范圍。

面試官:什么是死鎖?怎么避免和解決?

回答:

什么是死鎖?

死鎖是指兩個(gè)或多個(gè)事務(wù)在執(zhí)行過程中,因互相持有對(duì)方需要的鎖而造成的一種阻塞現(xiàn)象。如果沒有外力介入,這些事務(wù)都無法繼續(xù)執(zhí)行。

舉個(gè)經(jīng)典的例子:

-- 事務(wù)A
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;  -- 獲得id=1的X鎖
-- 此時(shí)事務(wù)A持有id=1的鎖,等待id=2的鎖

-- 事務(wù)B
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 2;  -- 獲得id=2的X鎖
UPDATE account SET balance = balance + 100 WHERE id = 1;  -- 等待id=1的鎖

-- 事務(wù)A繼續(xù)
UPDATE account SET balance = balance + 100 WHERE id = 2;  -- 等待id=2的鎖
-- 死鎖產(chǎn)生!A等B釋放id=2,B等A釋放id=1

如何檢測(cè)和處理死鎖?

InnoDB有兩種策略:

1. 等待超時(shí)參數(shù)innodb_lock_wait_timeout,默認(rèn)50秒。超時(shí)后事務(wù)會(huì)回滾。
缺點(diǎn):等待時(shí)間長(zhǎng),業(yè)務(wù)響應(yīng)慢。

2. 死鎖檢測(cè)(推薦)參數(shù)innodb_deadlock_detect=ON(默認(rèn)開啟)。
InnoDB會(huì)主動(dòng)檢測(cè)死鎖,發(fā)現(xiàn)后立即回滾其中一個(gè)代價(jià)較小的事務(wù)。

如何避免死鎖?

1. 按固定順序訪問資源所有業(yè)務(wù)代碼都按照相同的順序獲取鎖。比如都先鎖id小的,再鎖id大的。

2. 減小鎖的粒度和持有時(shí)間

  • 盡量使用行級(jí)鎖而不是表級(jí)鎖
  • 事務(wù)中的SQL盡量少,盡快提交
  • 避免在事務(wù)中進(jìn)行耗時(shí)操作(如調(diào)用外部接口)

3. 使用合理的索引如果查詢沒有走索引,InnoDB會(huì)進(jìn)行全表掃描并鎖住所有行,更容易死鎖。

4. 降低隔離級(jí)別如果業(yè)務(wù)允許,使用READ COMMITTED級(jí)別,沒有Gap Lock,死鎖概率降低。

5. 使用樂觀鎖代替悲觀鎖

-- 樂觀鎖方式
UPDATE product SET stock = stock - 1, version = version + 1 
WHERE id = 1 AND version = 10;

如何排查死鎖?

-- 查看最近一次死鎖信息
SHOW ENGINE INNODB STATUS;

-- 查看當(dāng)前鎖等待
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

面試官:說說樂觀鎖和悲觀鎖的區(qū)別,以及各自的使用場(chǎng)景

回答:

悲觀鎖

悲觀鎖的思想是:每次操作數(shù)據(jù)時(shí)都認(rèn)為會(huì)有并發(fā)沖突,所以先加鎖再操作。

實(shí)現(xiàn)方式:數(shù)據(jù)庫的鎖機(jī)制,如SELECT FOR UPDATE

START TRANSACTION;
-- 先加排他鎖
SELECT stock FROM product WHERE id = 1 FOR UPDATE;
-- 業(yè)務(wù)邏輯處理
UPDATE product SET stock = stock - 1 WHERE id = 1;
COMMIT;

特點(diǎn):

  • 數(shù)據(jù)安全性高
  • 性能開銷大(鎖等待、可能死鎖)
  • 適合寫多讀少的場(chǎng)景

樂觀鎖

樂觀鎖的思想是:假設(shè)沖突很少發(fā)生,不加鎖,而是在更新時(shí)檢查數(shù)據(jù)是否被修改過。

實(shí)現(xiàn)方式:通常用版本號(hào)或時(shí)間戳

-- 1. 先查詢數(shù)據(jù)和版本號(hào)
SELECT id, stock, version FROM product WHERE id = 1;
-- 假設(shè)返回 stock=100, version=1

-- 2. 更新時(shí)檢查版本號(hào)
UPDATE product 
SET stock = stock - 1, version = version + 1 
WHERE id = 1 AND version = 1;

-- 3. 檢查受影響行數(shù)
-- 如果是0,說明數(shù)據(jù)被其他事務(wù)修改過,需要重試

特點(diǎn):

  • 性能好(無鎖開銷)
  • 可能需要重試機(jī)制
  • 適合讀多寫少的場(chǎng)景

使用場(chǎng)景對(duì)比:

場(chǎng)景推薦使用原因
電商秒殺/庫存扣減樂觀鎖讀多寫少,用戶量大
銀行轉(zhuǎn)賬悲觀鎖資金安全優(yōu)先,寫操作頻繁
文章點(diǎn)贊/閱讀數(shù)樂觀鎖允許少量誤差,并發(fā)量大
訂單狀態(tài)流轉(zhuǎn)悲觀鎖/分布式鎖狀態(tài)一致性要求高

CAS(Compare And Swap):

樂觀鎖的本質(zhì)其實(shí)就是CAS思想:比較并交換。在Java中,Atomic類就是用CAS實(shí)現(xiàn)的。在數(shù)據(jù)庫中,我們用version字段來模擬CAS操作。

五、SQL優(yōu)化篇

面試官:如何定位和優(yōu)化慢SQL?

回答:

這是一個(gè)很實(shí)際的問題,我按照實(shí)際工作中的流程來說:

第一步:開啟慢查詢?nèi)罩荆ㄎ宦齋QL

-- 查看慢查詢?nèi)罩臼欠耖_啟
SHOW VARIABLES LIKE 'slow_query_log';

-- 開啟慢查詢?nèi)罩?
SET GLOBAL slow_query_log = ON;

-- 設(shè)置慢查詢閾值,比如2秒
SET GLOBAL long_query_time = 2;

-- 查看慢查詢?nèi)罩疚募恢?
SHOW VARIABLES LIKE 'slow_query_log_file';

在生產(chǎn)環(huán)境中,也可以通過監(jiān)控系統(tǒng)(如Prometheus+Grafana)來發(fā)現(xiàn)慢SQL。

第二步:使用EXPLAIN分析執(zhí)行計(jì)劃

EXPLAIN SELECT * FROM user WHERE username = 'zhangsan';

重點(diǎn)關(guān)注這幾個(gè)字段:

  • type:訪問類型,從好到差:system > const > eq_ref > ref > range > index > ALL

    • const:通過主鍵或唯一索引查詢,最多一行
    • ref:使用非唯一索引
    • range:索引范圍掃描
    • index:全索引掃描
    • ALL:全表掃描(最差,要優(yōu)化)
  • key:實(shí)際使用的索引,NULL表示沒用索引

  • rows:預(yù)估掃描的行數(shù),越少越好

  • Extra:額外信息

    • Using index:覆蓋索引,好
    • Using filesort:需要額外排序,需要優(yōu)化
    • Using temporary:使用臨時(shí)表,需要優(yōu)化

第三步:針對(duì)性優(yōu)化

1. 添加合適的索引

-- 為WHERE條件字段添加索引
CREATE INDEX idx_username ON user(username);

-- 為排序字段添加索引
CREATE INDEX idx_create_time ON user(create_time);

-- 聯(lián)合索引
CREATE INDEX idx_status_create_time ON order(status, create_time);

2. 優(yōu)化SQL寫法

-- 避免SELECT *
SELECT id, username, email FROM user WHERE id = 1;

-- 避免函數(shù)操作索引列
-- 不好
SELECT * FROM user WHERE YEAR(create_time) = 2024;
-- 好
SELECT * FROM user WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';

-- 避免隱式類型轉(zhuǎn)換
-- phone是varchar類型
-- 不好
SELECT * FROM user WHERE phone = 13800138000;
-- 好
SELECT * FROM user WHERE phone = '13800138000';

3. 分頁優(yōu)化

-- 深分頁問題
SELECT * FROM order LIMIT 1000000, 10;  -- 需要掃描100萬+10行

-- 優(yōu)化方案:延遲關(guān)聯(lián)
SELECT * FROM order 
WHERE id >= (SELECT id FROM order ORDER BY id LIMIT 1000000, 1) 
LIMIT 10;

-- 或者:記錄上次查詢的最后一個(gè)ID
SELECT * FROM order WHERE id > 1000000 ORDER BY id LIMIT 10;

4. 批量操作

-- 不好:循環(huán)單條插入
INSERT INTO user VALUES (1, 'a');
INSERT INTO user VALUES (2, 'b');

-- 好:批量插入
INSERT INTO user VALUES (1, 'a'), (2, 'b'), (3, 'c');

-- 大批量數(shù)據(jù)建議分批,每批500-1000條

5. 合理使用JOIN

-- 小表驅(qū)動(dòng)大表
-- 如果department表小,用IN
SELECT * FROM employee WHERE dept_id IN (SELECT id FROM department);

-- 如果employee表小,用EXISTS
SELECT * FROM employee e 
WHERE EXISTS (SELECT 1 FROM department d WHERE d.id = e.dept_id);

-- JOIN時(shí)確保關(guān)聯(lián)字段有索引
-- 控制JOIN的表數(shù)量,一般不超過3張

面試官:你在實(shí)際項(xiàng)目中做過哪些SQL優(yōu)化?能舉個(gè)具體的例子嗎?

回答:

好的,我舉一個(gè)之前處理過的真實(shí)案例。

背景:有一個(gè)訂單列表查詢接口,在數(shù)據(jù)量達(dá)到500萬后,查詢變得很慢,平均響應(yīng)時(shí)間超過5秒。

原始SQL:

SELECT * FROM order 
WHERE user_id = 12345 
  AND status IN (1, 2, 3) 
  AND create_time >= '2024-01-01' 
ORDER BY create_time DESC 
LIMIT 0, 20;

排查過程:

  • 首先用EXPLAIN分析,發(fā)現(xiàn)type是ALL,全表掃描,沒走索引
  • 檢查發(fā)現(xiàn)表上只有主鍵索引,沒有針對(duì)查詢條件的索引

優(yōu)化方案:

第一步:添加聯(lián)合索引

CREATE INDEX idx_user_status_time ON order(user_id, status, create_time);

添加后,type變成了range,rows從500萬降到了幾千。

**第二步:優(yōu)化SELECT ***

-- 改成只查需要的字段
SELECT id, order_no, status, amount, create_time 
FROM order WHERE ...

第三步:考慮覆蓋索引

因?yàn)椴樵兊淖侄伪容^多,創(chuàng)建覆蓋索引不太現(xiàn)實(shí)。但對(duì)于一些只需要少量字段的查詢,可以考慮。

第四步:分頁優(yōu)化

原來的分頁到后面頁數(shù)時(shí)會(huì)很慢:

-- 原來的,第1000頁時(shí)要掃描20000行
SELECT ... LIMIT 19980, 20;

-- 優(yōu)化后,使用游標(biāo)分頁
SELECT ... WHERE id < 上一頁最后一條的id ORDER BY id DESC LIMIT 20;

優(yōu)化效果:

  • 查詢時(shí)間從5秒+降到50毫秒以內(nèi)
  • 支撐了日均百萬級(jí)的查詢量

總結(jié)幾個(gè)要點(diǎn):

  • 一定要有合適的索引
  • 聯(lián)合索引要考慮查詢條件的順序和區(qū)分度
  • 避免SELECT *
  • 深分頁要特別處理
  • 定期EXPLAIN分析關(guān)鍵SQL

六、其他高頻問題

面試官:MySQL主從復(fù)制的原理是什么?

回答:

MySQL主從復(fù)制是通過binlog來實(shí)現(xiàn)的,整個(gè)過程可以分為三個(gè)步驟:

第一步:Master記錄binlog主庫執(zhí)行寫操作時(shí)(INSERT/UPDATE/DELETE),會(huì)把變更記錄到binlog(二進(jìn)制日志)中。

第二步:從庫IO線程讀取binlog從庫有一個(gè)IO線程,它會(huì)連接到主庫,讀取主庫的binlog,然后寫入到從庫本地的relay log(中繼日志)中。

第三步:從庫SQL線程執(zhí)行relay log從庫還有一個(gè)SQL線程,它會(huì)讀取relay log中的事件,在從庫上重新執(zhí)行一遍,從而實(shí)現(xiàn)數(shù)據(jù)同步。

整個(gè)流程:

Master寫數(shù)據(jù) → 寫入binlog → 從庫IO線程讀取 → 寫入relay log → 從庫SQL線程執(zhí)行 → 數(shù)據(jù)同步

binlog的三種格式:

  • STATEMENT:記錄SQL語句本身

    • 優(yōu)點(diǎn):日志量小
    • 缺點(diǎn):某些函數(shù)(如NOW()、UUID())可能導(dǎo)致主從數(shù)據(jù)不一致
  • ROW:記錄每行數(shù)據(jù)的變化

    • 優(yōu)點(diǎn):不會(huì)出現(xiàn)數(shù)據(jù)不一致
    • 缺點(diǎn):日志量大
  • MIXED:混合模式,MySQL自動(dòng)選擇

    • 一般語句用STATEMENT,特殊情況用ROW

推薦使用ROW格式,雖然日志量大,但數(shù)據(jù)一致性有保證。

主從延遲的原因和解決方案:

原因:

  • 主庫并發(fā)寫入,從庫單線程重放
  • 網(wǎng)絡(luò)延遲
  • 從庫機(jī)器性能差
  • 大事務(wù)

解決方案:

  • 使用并行復(fù)制(MySQL 5.6+)
  • 從庫配置更好的硬件
  • 避免大事務(wù)
  • 讀寫分離時(shí),對(duì)實(shí)時(shí)性要求高的讀走主庫

面試官:如何保證MySQL和Redis緩存的數(shù)據(jù)一致性?

回答:

這是一個(gè)經(jīng)典問題,我來說說常見的幾種策略:

策略一:Cache Aside Pattern(旁路緩存模式)

這是最常用的策略:

  • 讀:先讀緩存,緩存沒有再讀數(shù)據(jù)庫,然后寫入緩存
  • 寫:先更新數(shù)據(jù)庫,再刪除緩存
// 讀取
public User getUser(Long id) {
    User user = redis.get("user:" + id);
    if (user == null) {
        user = db.getUser(id);
        redis.set("user:" + id, user);
    }
    return user;
}

// 寫入
public void updateUser(User user) {
    db.update(user);
    redis.delete("user:" + user.getId());
}

為什么是刪除緩存而不是更新緩存?

  • 更新緩存可能有并發(fā)問題:A先更新DB,B后更新DB,但B先更新緩存,A后更新緩存,導(dǎo)致緩存是舊數(shù)據(jù)
  • 刪除更簡(jiǎn)單,讓下次讀取時(shí)重新加載

為什么是先更新DB再刪緩存,而不是先刪緩存再更新DB?

  • 先刪緩存的問題:刪除緩存后、更新DB前,另一個(gè)請(qǐng)求讀到舊數(shù)據(jù)并寫入緩存,導(dǎo)致臟數(shù)據(jù)

策略一的問題:即使先更新DB再刪緩存,極端情況下仍可能不一致:

  • 緩存剛好失效
  • 請(qǐng)求A讀DB(舊值)
  • 請(qǐng)求B更新DB(新值)
  • 請(qǐng)求B刪除緩存
  • 請(qǐng)求A把舊值寫入緩存

不過這種情況概率很低,因?yàn)閷懖僮魍ǔ1茸x操作慢。

策略二:延遲雙刪

為了解決上面的極端情況:

public void updateUser(User user) {
    redis.delete("user:" + user.getId());  // 第一次刪除
    db.update(user);
    Thread.sleep(500);  // 等待一段時(shí)間
    redis.delete("user:" + user.getId());  // 第二次刪除
}

延遲時(shí)間要大于一次讀操作的時(shí)間。

策略三:異步更新緩存(基于消息隊(duì)列或binlog)

寫操作 → 更新DB → 發(fā)送消息/監(jiān)聽binlog → 異步更新/刪除緩存

使用Canal監(jiān)聽binlog的方式更可靠,缺點(diǎn)是有一定延遲。

總結(jié):

  • 一般場(chǎng)景:Cache Aside Pattern + 設(shè)置緩存過期時(shí)間
  • 要求較高:延遲雙刪
  • 強(qiáng)一致性要求:分布式事務(wù)或不用緩存

面試官:最后一個(gè)問題,你覺得一個(gè)合格的索引應(yīng)該怎么設(shè)計(jì)?

回答:

這個(gè)問題很好,我總結(jié)一下索引設(shè)計(jì)的核心原則:

1. 選擇合適的字段建索引

  • 高頻查詢條件字段:WHERE后面經(jīng)常出現(xiàn)的字段
  • 排序字段:ORDER BY的字段
  • 連接字段:JOIN ON的字段
  • 高區(qū)分度字段:值的種類多,重復(fù)少(如用戶ID),而不是像性別這種

2. 聯(lián)合索引的設(shè)計(jì)原則

  • 最左前綴:把最常用的查詢條件放在最左邊
  • 區(qū)分度高的在前:區(qū)分度高的字段放前面,可以更快過濾數(shù)據(jù)
  • 范圍查詢放最后:范圍查詢會(huì)使后面的字段無法使用索引
  • 考慮覆蓋索引:盡量讓查詢的字段都在索引中
-- 假設(shè)查詢條件是:WHERE status = 1 AND type = 2 AND create_time > '2024-01-01'
-- 好的設(shè)計(jì):idx_status_type_time(status, type, create_time)
-- 等值查詢?cè)谇?,范圍查詢?cè)诤?/pre>

3. 避免過多索引

  • 索引不是越多越好,會(huì)影響寫入性能
  • 一般一張表的索引控制在5個(gè)以內(nèi)
  • 定期清理無用索引

4. 主鍵索引的設(shè)計(jì)

  • 推薦使用自增ID,順序插入效率高
  • 分布式場(chǎng)景用雪花算法,也是趨勢(shì)遞增的
  • 避免使用UUID或隨機(jī)字符串,會(huì)導(dǎo)致頁分裂

5. 避免冗余索引

-- 冗余
INDEX idx_a (a)
INDEX idx_a_b (a, b)  -- idx_a是冗余的,idx_a_b可以覆蓋它

-- 不冗余
INDEX idx_a_b (a, b)
INDEX idx_b_a (b, a)  -- 順序不同,不冗余

6. 使用前綴索引壓縮長(zhǎng)字段

-- 對(duì)于很長(zhǎng)的字符串字段
CREATE INDEX idx_email ON user(email(10));  -- 只索引前10個(gè)字符

7. 實(shí)踐建議

  • 先有業(yè)務(wù)查詢,再設(shè)計(jì)索引(不要憑空設(shè)計(jì))
  • 定期用EXPLAIN檢查關(guān)鍵SQL
  • 用慢查詢?nèi)罩景l(fā)現(xiàn)問題
  • 數(shù)據(jù)量小的表不需要太多索引

總結(jié) 

到此這篇關(guān)于MySQL存粹問題面試準(zhǔn)備總結(jié)的文章就介紹到這了,更多相關(guān)MySQL面試準(zhǔn)備內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 在MySQL中奏響數(shù)據(jù)庫操作的樂章(推薦)

    在MySQL中奏響數(shù)據(jù)庫操作的樂章(推薦)

    本文詳細(xì)介紹了如何在MySQL中進(jìn)行數(shù)據(jù)庫操作,包括創(chuàng)建、刪除、修改數(shù)據(jù)庫等,以及如何使用字符集和校驗(yàn)規(guī)則,以及備份和恢復(fù)數(shù)據(jù)庫的方法,同時(shí),還討論了如何查看和修改數(shù)據(jù)庫的結(jié)構(gòu)和數(shù)據(jù),總的來說,本文為讀者提供了一份全面的MySQL數(shù)據(jù)庫操作指南
    2024-10-10
  • Linux下實(shí)現(xiàn)MySQL數(shù)據(jù)備份和恢復(fù)的命令使用全攻略

    Linux下實(shí)現(xiàn)MySQL數(shù)據(jù)備份和恢復(fù)的命令使用全攻略

    這篇文章主要介紹了Linux下實(shí)現(xiàn)MySQL數(shù)據(jù)備份和恢復(fù)的命令使用全攻略,包括使用Mysqldump和LVM快照以及xtrabackup三種方法,傾力推薦!需要的朋友可以參考下
    2015-11-11
  • 淺談mysqldump使用方法(MySQL數(shù)據(jù)庫的備份與恢復(fù))

    淺談mysqldump使用方法(MySQL數(shù)據(jù)庫的備份與恢復(fù))

    下面小編就為大家?guī)硪黄獪\談mysqldump使用方法(MySQL數(shù)據(jù)庫的備份與恢復(fù))。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-01-01
  • MySQL數(shù)據(jù)庫大小寫敏感的問題

    MySQL數(shù)據(jù)庫大小寫敏感的問題

    今天小編就為大家分享一篇關(guān)于MySQL數(shù)據(jù)庫大小寫敏感的問題,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • MySQL數(shù)據(jù)庫簡(jiǎn)介與基本操作

    MySQL數(shù)據(jù)庫簡(jiǎn)介與基本操作

    這篇文章介紹了MySQL數(shù)據(jù)庫與其基本操作,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-05-05
  • mysql下修改engine引擎的方法

    mysql下修改engine引擎的方法

    修改mysql的引擎為INNODB,可以使用外鍵,事務(wù)等功能,性能高。
    2011-08-08
  • CentOS7卸載MySQL5.7的方法步驟

    CentOS7卸載MySQL5.7的方法步驟

    這篇文章主要介紹了CentOS7卸載MySQL5.7的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • MySQL數(shù)據(jù)庫21條最佳性能優(yōu)化經(jīng)驗(yàn)

    MySQL數(shù)據(jù)庫21條最佳性能優(yōu)化經(jīng)驗(yàn)

    數(shù)據(jù)庫的操作越來越成為整個(gè)應(yīng)用的性能瓶頸了,這點(diǎn)對(duì)于Web應(yīng)用尤其明顯。這篇文章主要介紹了MySQL數(shù)據(jù)庫21條最佳性能優(yōu)化經(jīng)驗(yàn)的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • Mysql教程分組排名實(shí)現(xiàn)示例詳解

    Mysql教程分組排名實(shí)現(xiàn)示例詳解

    這篇文章主要為大家介紹了Mysql數(shù)據(jù)庫分組排名實(shí)現(xiàn)的示例詳解教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2021-10-10
  • MySQL深入淺出精講觸發(fā)器用法

    MySQL深入淺出精講觸發(fā)器用法

    觸發(fā)器是SQLserver提供給程序員和數(shù)據(jù)分析員來保證數(shù)據(jù)完整性的一種方法,它是與表事件相關(guān)的特殊的存儲(chǔ)過程,事件是在 MySQL 5.1后引入的,有點(diǎn)類似操作系統(tǒng)的計(jì)劃任務(wù),但是周期性任務(wù)是內(nèi)置在MySQL服務(wù)端執(zhí)行的
    2022-08-08

最新評(píng)論

河西区| 贵港市| 澄城县| 湾仔区| 卢湾区| 彭山县| 抚松县| 玉龙| 镇平县| 鱼台县| 高邑县| 钦州市| 吉木萨尔县| 句容市| 牟定县| 普陀区| 龙口市| 澄城县| 浑源县| 厦门市| 芜湖市| 新野县| 若尔盖县| 苍南县| 万宁市| 巴青县| 武邑县| 阳春市| 新和县| 北宁市| 云安县| 汉寿县| 武冈市| 贵港市| 南乐县| 寿光市| 三明市| 济南市| 宿州市| 德清县| 桓仁|