Mysql面試題

文章目錄

  1. MySQL 索引使用有哪些注意事項呢?
    索引哪些情況會失效
    索引不適合哪些場景

  2. MySQL 遇到過死鎖問題嗎,你是如何解決的?

  3. 日常工作中你是怎么優(yōu)化SQL的?

  4. 分庫分表的設(shè)計
    分庫分表方案
    常用的分庫分表中間件
    分庫分表可能遇到的問題

  5. InnoDB與MyISAM的區(qū)別

  6. 數(shù)據(jù)庫索引的原理,為什么要用 B+樹,為什么不用二叉樹?
    為什么不是一般二叉樹?
    為什么不是平衡二叉樹呢?
    那為什么不是B樹而是B+樹呢?

  7. 聚集索引與非聚集索引的區(qū)別
    何時使用聚集索引或非聚集索引?

  8. limit 1000000 加載很慢的話,你是怎么解決的呢?

  9. 如何選擇合適的分布式主鍵方案呢?

  10. 事務(wù)的隔離級別有哪些?MySQL的默認隔離級別是什么?
    讀未提交(Read Uncommitted)
    串行化(Serializable)
    可重復(fù)讀(Repeated Read, RR) 這是InnoDB默認的隔離級別,在RR下:
    讀提交(Read Committed, RC) 這是互聯(lián)網(wǎng)最常用的隔離級別,在RC下:

  11. 在高并發(fā)情況下,如何做到安全的修改同一行數(shù)據(jù)?
    使用悲觀鎖
    使用樂觀鎖

  12. 數(shù)據(jù)庫的樂觀鎖和悲觀鎖
    悲觀鎖
    樂觀鎖

  13. SQL優(yōu)化的一般步驟是什么,怎么看執(zhí)行計劃(explain),如何理解其中各個字段的含義?

  14. select for update有什么含義,會鎖表還是鎖行還是其他?

  15. MySQL事務(wù)得四大特性以及實現(xiàn)原理
    事務(wù)ACID特性的實現(xiàn)思想

  16. 如果某個表有近千萬數(shù)據(jù),CRUD比較慢,如何優(yōu)化?
    分庫分表
    索引優(yōu)化

  17. 如何寫sql能夠有效的使用到復(fù)合索引?

  18. mysql中in 和exists的區(qū)別

  19. 數(shù)據(jù)庫自增主鍵可能遇到什么問題?

  20. MVCC底層原理

  21. 數(shù)據(jù)庫中間件了解過嗎,sharding jdbc,mycat?

  22. MySQL的主從延遲,你怎么解決?
    主從復(fù)制分了五個步驟進行:
    主從同步延遲的原因
    主從同步延遲的解決辦法

  23. 說一下大表查詢的優(yōu)化方案

  24. 什么是數(shù)據(jù)庫連接池?為什么需要數(shù)據(jù)庫連接池呢?
    應(yīng)用程序和數(shù)據(jù)庫建立連接的過程
    數(shù)據(jù)庫連接池好處

  25. 一條SQL語句在MySQL中如何執(zhí)行的?
    MySQL邏輯架構(gòu)圖
    連接器
    查詢緩存
    分析器
    優(yōu)化器
    執(zhí)行器

  26. InnoDB引擎中的索引策略,了解過嗎?
    獨立的列
    前綴索引和索引選擇性
    多列索引
    選擇合適的索引列順序
    聚簇索引
    列的離散性

  27. 數(shù)據(jù)庫存儲日期格式時,如何考慮時區(qū)轉(zhuǎn)換問題?

  28. 一條sql執(zhí)行過長的時間,你如何優(yōu)化,從哪些方面入手?

  29. Blob和text有什么區(qū)別?

  30. MySQL里記錄貨幣用什么字段類型比較好?

  31. InnoDB有哪幾種鎖?
    共享/排它鎖(Shared and Exclusive Locks)
    意向鎖(Intention Locks)
    記錄鎖(Record Locks)
    間隙鎖(Gap Locks)
    臨鍵鎖(Next-key Locks)
    插入意向鎖(Insert Intention Locks)
    自增鎖(Auto-inc Locks)

  32. Hash索引和B+樹區(qū)別是什么?你在設(shè)計索引是怎么抉擇的?

  33. mysql 的內(nèi)連接、左連接、右連接有什么區(qū)別?

  34. 什么是內(nèi)連接、外連接、交叉連接、笛卡爾積呢?

  35. 說一下數(shù)據(jù)庫的三大范式

  36. mysql有關(guān)權(quán)限的表有哪幾個呢?
    主從復(fù)制binlog格式有哪幾種?有什么區(qū)別?

  37. Mysql主從復(fù)制方式?有什么區(qū)別?

  38. InnoDB內(nèi)存結(jié)構(gòu)包含四大核心組件

  39. 索引有哪些優(yōu)缺點?
    優(yōu)點
    缺點

  40. 索引有哪幾種類型?

  41. 創(chuàng)建索引的三種方式
    在執(zhí)行CREATE TABLE時創(chuàng)建索引
    使用ALTER TABLE命令添加索引
    使用CREATE INDEX命令創(chuàng)建

  42. 百萬級別或以上的數(shù)據(jù),你是如何刪除的?

  43. 覆蓋索引、回表等這些,了解過嗎?

  44. B+樹在滿足聚簇索引和覆蓋索引的時候不需要回表查詢數(shù)據(jù)?

  45. 何時使用聚簇索引與非聚簇索引

  46. 非聚簇索引一定會回表查詢嗎?

  47. 組合索引是什么?為什么需要注意組合索引中的順序?

  48. 什么是死鎖?怎么解決?

  49. 你是如何監(jiān)控你們的數(shù)據(jù)庫的?你們的慢日志都是怎么查詢的?
    MySQL技術(shù)體系思維導(dǎo)圖

  50. MySQL 索引使用有哪些注意事項呢?
    可以從兩個維度回答這個問題:索引哪些情況會失效,索引不適合哪些場景

索引哪些情況會失效
查詢條件包含or,會導(dǎo)致索引失效。
隱式類型轉(zhuǎn)換,會導(dǎo)致索引失效,例如age字段類型是int,我們where age = “1”,這樣就會觸發(fā)隱式類型轉(zhuǎn)換。
like通配符會導(dǎo)致索引失效。注意:"ABC%“會走range索引,”%ABC"索引才會失效。
聯(lián)合索引,查詢時的條件列不是聯(lián)合索引中的第一個列,索引失效。
對索引字段進行函數(shù)運算。
對索引列運算(如,+、-、*、/),索引失效。
索引字段上使用(!= 或者 < >,not in)時,會導(dǎo)致索引失效。
索引字段上使用is null, is not null,可能導(dǎo)致索引失效。
相join的兩個表的字符編碼不同,不能命中索引,會導(dǎo)致笛卡爾積的循環(huán)計算
mysql估計使用全表掃描要比使用索引快,則不使用索引。
索引不適合哪些場景
數(shù)據(jù)量少的不適合加索引
更新比較頻繁的也不適合加索引
離散性低的字段不適合加索引(如性別)

  1. MySQL 遇到過死鎖問題嗎,你是如何解決的?
    排查死鎖的步驟:

查看死鎖日志show engine innodb status;
找出死鎖Sql
分析sql加鎖情況
模擬死鎖案發(fā)
分析死鎖日志
分析死鎖結(jié)果

  1. 日常工作中你是怎么優(yōu)化SQL的?
    可以從這幾個維度回答這個問題:

加索引
避免返回不必要的數(shù)據(jù)
適當分批量進行
優(yōu)化sql結(jié)構(gòu)
主從架構(gòu),提升讀性能
分庫分表

  1. 分庫分表的設(shè)計
    分庫分表方案,分庫分表中間件,分庫分表可能遇到的問題

分庫分表方案
水平分庫:以字段為依據(jù),按照一定策略(hash、range等),將一個庫中的數(shù)據(jù)拆分到多個庫中。
水平分表:以字段為依據(jù),按照一定策略(hash、range等),將一個表中的數(shù)據(jù)拆分到多個表中。
垂直分庫:以表為依據(jù),按照業(yè)務(wù)歸屬不同,將不同的表拆分到不同的庫中。
垂直分表:以字段為依據(jù),按照字段的活躍性,將表中字段拆到不同的表(主表和擴展表)中。
常用的分庫分表中間件
sharding-jdbc
Mycat
分庫分表可能遇到的問題
事務(wù)問題:需要用分布式事務(wù)啦
跨節(jié)點Join的問題:解決這一問題可以分兩次查詢實現(xiàn)
跨節(jié)點的count,order by,group by以及聚合函數(shù)問題:分別在各個節(jié)點上得到結(jié)果后在應(yīng)用程序端進行合并。
數(shù)據(jù)遷移,容量規(guī)劃,擴容等問題
ID問題:數(shù)據(jù)庫被切分后,不能再依賴數(shù)據(jù)庫自身的主鍵生成機制啦,最簡單可以考慮UUID
跨分片的排序分頁問題(后臺加大pagesize處理?)

  1. InnoDB與MyISAM的區(qū)別
    InnoDB支持事務(wù),MyISAM不支持事務(wù)
    InnoDB支持外鍵,MyISAM不支持外鍵
    InnoDB 支持 MVCC(多版本并發(fā)控制),MyISAM 不支持
    select count(*) from table時,MyISAM更快,因為它有一個變量保存了整個表的總行數(shù),可以直接讀取,InnoDB就需要全表掃描。
    Innodb不支持全文索引,而MyISAM支持全文索引(5.7以后的InnoDB也支持全文索引)
    InnoDB支持表、行級鎖,而MyISAM支持表級鎖。
    InnoDB表必須有主鍵,而MyISAM可以沒有主鍵
    Innodb表需要更多的內(nèi)存和存儲,而MyISAM可被壓縮,存儲空間較小,。
    Innodb按主鍵大小有序插入,MyISAM記錄插入順序是,按記錄插入順序保存。
    InnoDB 存儲引擎提供了具有提交、回滾、崩潰恢復(fù)能力的事務(wù)安全,與 MyISAM 比 InnoDB 寫的效率差一些,并且會占用更多的磁盤空間以保留數(shù)據(jù)和索引
    InnoDB 屬于索引組織表,使用共享表空間和多表空間儲存數(shù)據(jù)。MyISAM用.frm、.MYD、.MTI來儲存表定義,數(shù)據(jù)和索引。
  2. 數(shù)據(jù)庫索引的原理,為什么要用 B+樹,為什么不用二叉樹?
    可以從幾個維度去看這個問題,查詢是否夠快,效率是否穩(wěn)定,存儲數(shù)據(jù)多少,以及查找磁盤次數(shù),為什么不是二叉樹,為什么不是平衡二叉樹,為什么不是B樹,而偏偏是B+樹呢?

為什么不是一般二叉樹?
1)當數(shù)據(jù)量大時,樹的高度會比較高(樹的高度決定著它的IO操作次數(shù),IO操作耗時大),查詢會比較慢。
2)每個磁盤塊(節(jié)點/頁)保存的數(shù)據(jù)太小(IO本來是耗時操作,每次IO只能讀取到一個關(guān)鍵字,顯然不合適),沒有很好的利用操作磁盤IO的數(shù)據(jù)交換特性,也沒有利用好磁盤IO的預(yù)讀能力(空間局部性原理),從而帶來頻繁的IO操作。

為什么不是平衡二叉樹呢?
我們知道,在內(nèi)存比在磁盤的數(shù)據(jù),查詢效率快得多。如果樹這種數(shù)據(jù)結(jié)構(gòu)作為索引,那我們每查找一次數(shù)據(jù)就需要從磁盤中讀取一個節(jié)點,也就是我們說的一個磁盤塊,但是平衡二叉樹可是每個節(jié)點只存儲一個鍵值和數(shù)據(jù)的,如果是B樹,可以存儲更多的節(jié)點數(shù)據(jù),樹的高度也會降低,因此讀取磁盤的次數(shù)就降下來啦,查詢效率就快啦。

那為什么不是B樹而是B+樹呢?
1)B+Tree范圍查找,定位min與max之后,中間葉子節(jié)點,就是結(jié)果集,不用中序回溯
2)B+Tree磁盤讀寫能力更強(葉子節(jié)點不保存真實數(shù)據(jù),因此一個磁盤塊能保存的關(guān)鍵字更多,因此每次加載的關(guān)鍵字越多)
3)B+Tree掃表和掃庫能力更強(B-Tree樹需要掃描整顆樹,B+Tree樹只需要掃描葉子節(jié)點)

詳細參考:索引原理

  1. 聚集索引與非聚集索引的區(qū)別
    一個表中只能擁有一個聚集索引,而非聚集索引一個表可以存在多個。
    聚集索引,索引中鍵值的邏輯順序決定了表中相應(yīng)行的物理順序;非聚集索引,索引中索引的邏輯順序與磁盤上行的物理存儲順序不同。
    索引是通過二叉樹的數(shù)據(jù)結(jié)構(gòu)來描述的,我們可以這么理解聚簇索引:索引的葉節(jié)點就是數(shù)據(jù)節(jié)點。而非聚簇索引的葉節(jié)點仍然是索引節(jié)點,只不過有一個指針指向?qū)?yīng)的數(shù)據(jù)塊。
    聚集索引:物理存儲按照索引排序;非聚集索引:物理存儲不按照索引排序;
    何時使用聚集索引或非聚集索引?
  1. limit 1000000 加載很慢的話,你是怎么解決的呢?
    方案一:如果id是連續(xù)的,可以這樣,返回上次查詢的最大記錄(偏移量),再往下limit

select id,name from employee where id>1000000 limit 10.

方案二:在業(yè)務(wù)允許的情況下限制頁數(shù):
建議跟業(yè)務(wù)討論,有沒有必要查這么后的分頁啦。因為絕大多數(shù)用戶都不會往后翻太多頁。

方案三:order by + 索引(id為索引)

select id,name from employee order by id limit 1000000,10
SELECT a.* FROM employee a, (select id from employee where 條件 LIMIT 1000000,10 ) b where a.id=b.id

方案四:利用延遲關(guān)聯(lián)或者子查詢優(yōu)化超多分頁場景。(先快速定位需要獲取的id段,然后再關(guān)聯(lián))

  1. 如何選擇合適的分布式主鍵方案呢?
    數(shù)據(jù)庫自增長序列或字段。
    UUID
    雪花算法
    Redis生成ID
    利用zookeeper生成唯一ID
  2. 事務(wù)的隔離級別有哪些?MySQL的默認隔離級別是什么?
    什么是事務(wù)的隔離性?

隔離性是指,多個用戶的并發(fā)事務(wù)訪問同一個數(shù)據(jù)庫時,一個用戶的事務(wù)不應(yīng)該被其他用戶的事務(wù)干擾,多個并發(fā)事務(wù)之間要相互隔離。

咱們舉例子來說明:

建表語句:

CREATE TABLE `T`  (
  `id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE = INNODB;

數(shù)據(jù)列表:

id  name
1   xiaohong
2   zhangsan
3   lisi
案例一:

事務(wù)A,先執(zhí)行,處于未提交的狀態(tài):

insert into T values(4, wangwu); 

事務(wù)B,后執(zhí)行,也未提交:

select * from T; 

如果事務(wù)B能夠讀取到(4, wangwu)這條記錄,事務(wù)A就對事務(wù)B產(chǎn)生了影響,這個影響叫做“讀臟”,讀到了未提交事務(wù)操作的記錄。

案例二:

事務(wù)A,先執(zhí)行:

select * from T where id=1; 

結(jié)果集為:1, xiaohong

事務(wù)B,后執(zhí)行,并且提交:

update T set name=hzy where id=1; 

commit;

事務(wù)A,再次執(zhí)行相同的查詢:

select * from T where id=1; 

結(jié)果集為:1, hzy

這次是已提交事務(wù)B對事務(wù)A產(chǎn)生的影響,這個影響叫做“不可重復(fù)讀”,一個事務(wù)內(nèi)相同的查詢,得到了不同的結(jié)果。

案例三:

事務(wù)A,先執(zhí)行:

select * from T where id>3; 

結(jié)果集為: NULL

事務(wù)B,后執(zhí)行,并且提交:

insert into T values(4, wangwu); 
commit; 

事務(wù)A,首次查詢了id>3的結(jié)果為NULL,于是想插入一條為4的記錄:

insert into T values(4, hzy); 

結(jié)果集為: Error : duplicate key!

這次是已提交事務(wù)B對事務(wù)A產(chǎn)生的影響,這個影響叫做“幻讀”。

可以看到,并發(fā)的事務(wù)可能導(dǎo)致其他事務(wù):

讀臟

不可重復(fù)讀

幻讀

InnoDB實現(xiàn)了四種不同事務(wù)的隔離級別:

讀未提交(Read Uncommitted)
讀提交(Read Committed, RC)
可重復(fù)讀(Repeated Read, RR)
串行化(Serializable)
不同事務(wù)的隔離級別,實際上是一致性與并發(fā)性的一個權(quán)衡與折衷。

InnoDB的四種事務(wù)的隔離級別,分別是怎么實現(xiàn)的?

InnoDB使用不同的鎖策略(Locking Strategy)來實現(xiàn)不同的隔離級別。

讀未提交(Read Uncommitted)
這種事務(wù)隔離級別下,select語句不加鎖。

此時,可能讀取到不一致的數(shù)據(jù),即“讀臟”。這是并發(fā)最高,一致性最差的隔離級別。

串行化(Serializable)
這種事務(wù)的隔離級別下,所有select語句都會被隱式的轉(zhuǎn)化為select … in share mode.

這可能導(dǎo)致,如果有未提交的事務(wù)正在修改某些行,所有讀取這些行的select都會被阻塞住。

這是一致性最好的,但并發(fā)性最差的隔離級別。 在互聯(lián)網(wǎng)大數(shù)據(jù)量,高并發(fā)量的場景下,幾乎不會使用上述兩種隔離級別。

可重復(fù)讀(Repeated Read, RR) 這是InnoDB默認的隔離級別,在RR下:
①普通的select使用快照讀(snapshot read),這是一種不加鎖的一致性讀(Consistent Nonlocking Read),底層使用MVCC來實現(xiàn);

②加鎖的select(select … in share mode / select … for update), update, delete等語句,它們的鎖,依賴于它們是否在唯一索引(unique index)上使用了唯一的查詢條件(unique search condition),或者范圍查詢條件(range-type search condition):

在唯一索引上使用唯一的查詢條件,會使用記錄鎖(record lock),而不會封鎖記錄之間的間隔,即不會使用間隙鎖(gap lock)與臨鍵鎖(next-key lock)
范圍查詢條件,會使用間隙鎖與臨鍵鎖,鎖住索引記錄之間的范圍,避免范圍間插入記錄,以避免產(chǎn)生幻影行記錄,以及避免不可重復(fù)的讀
讀提交(Read Committed, RC) 這是互聯(lián)網(wǎng)最常用的隔離級別,在RC下:
①普通讀是快照讀;

②加鎖的select, update, delete等語句,除了在外鍵約束檢查(foreign-key constraint checking)以及重復(fù)鍵檢查(duplicate-key checking)時會封鎖區(qū)間,其他時刻都只使用記錄鎖;

此時,其他事務(wù)的插入依然可以執(zhí)行,就可能導(dǎo)致,讀取到幻影記錄。

  1. 在高并發(fā)情況下,如何做到安全的修改同一行數(shù)據(jù)?
    要安全的修改同一行數(shù)據(jù),就要保證一個線程在修改時其它線程無法更新這行記錄。一般有悲觀鎖和樂觀鎖兩種方案

使用悲觀鎖
悲觀鎖思想就是,當前線程要進來修改數(shù)據(jù)時,別的線程都得拒之門外~ 比如,可以使用select…for update

select * from User where name=‘jay’ for update
1
以上這條sql語句會鎖定了User表中所有符合檢索條件(name=‘jay’)的記錄。本次事務(wù)提交之前,別的線程都無法修改這些記錄。

使用樂觀鎖
樂觀鎖思想就是,有線程過來,先放過去修改,如果看到別的線程沒修改過,就可以修改成功,如果別的線程修改過,就修改失敗或者重試。實現(xiàn)方式:樂觀鎖一般會使用版本號機制或CAS算法實現(xiàn)。

  1. 數(shù)據(jù)庫的樂觀鎖和悲觀鎖
    悲觀鎖
    悲觀鎖她專一且缺乏安全感了,她的心只屬于當前事務(wù),每時每刻都擔心著它心愛的數(shù)據(jù)可能被別的事務(wù)修改,所以一個事務(wù)擁有(獲得)悲觀鎖后,其他任何事務(wù)都不能對數(shù)據(jù)進行修改啦,只能等待鎖被釋放才可以執(zhí)行。

樂觀鎖
樂觀鎖的“樂觀情緒”體現(xiàn)在,它認為數(shù)據(jù)的變動不會太頻繁。因此,它允許多個事務(wù)同時對數(shù)據(jù)進行變動。實現(xiàn)方式:樂觀鎖一般會使用版本號機制或CAS算法實現(xiàn)。

  1. SQL優(yōu)化的一般步驟是什么,怎么看執(zhí)行計劃(explain),如何理解其中各個字段的含義?
    show status 命令了解各種 sql 的執(zhí)行頻率
    通過慢查詢?nèi)罩径ㄎ荒切﹫?zhí)行效率較低的 sql 語句
    explain 分析低效 sql 的執(zhí)行計劃(這點非常重要,日常開發(fā)中用它分析Sql,會大大降低Sql導(dǎo)致的線上事故)
  2. select for update有什么含義,會鎖表還是鎖行還是其他?
    select for update 含義

select查詢語句是不會加鎖的,但是select for update除了有查詢的作用外,還會加鎖呢,而且它是悲觀鎖哦。至于加了是行鎖還是表鎖,這就要看是不是用了索引/主鍵啦。 沒用索引/主鍵的話就是表鎖,否則就是是行鎖。

  1. MySQL事務(wù)得四大特性以及實現(xiàn)原理
    原子性: 事務(wù)作為一個整體被執(zhí)行,包含在其中的對數(shù)據(jù)庫的操作要么全部被執(zhí)行,要么都不執(zhí)行。
    一致性: 指在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)不會被破壞,假如A賬戶給B賬戶轉(zhuǎn)10塊錢,不管成功與否,A和B的總金額是不變的。
    隔離性: 多個事務(wù)并發(fā)訪問時,事務(wù)之間是相互隔離的,即一個事務(wù)不影響其它事務(wù)運行效果。簡言之,就是事務(wù)之間是進水不犯河水的。
    持久性: 表示事務(wù)完成以后,該事務(wù)對數(shù)據(jù)庫所作的操作更改,將持久地保存在數(shù)據(jù)庫之中。
    事務(wù)ACID特性的實現(xiàn)思想
    原子性:是使用 undo log來實現(xiàn)的,如果事務(wù)執(zhí)行過程中出錯或者用戶執(zhí)行了rollback,系統(tǒng)通過undo log日志返回事務(wù)開始的狀態(tài)。
    持久性:使用 redo log來實現(xiàn),只要redo log日志持久化了,當系統(tǒng)崩潰,即可通過redo log把數(shù)據(jù)恢復(fù)。
    隔離性:通過鎖以及MVCC,使事務(wù)相互隔離開。
    一致性:通過回滾、恢復(fù),以及并發(fā)情況下的隔離性,從而實現(xiàn)一致性。
  2. 如果某個表有近千萬數(shù)據(jù),CRUD比較慢,如何優(yōu)化?
    分庫分表
    某個表有近千萬數(shù)據(jù),可以考慮優(yōu)化表結(jié)構(gòu),分表(水平分表,垂直分表),當然,你這樣回答,需要準備好面試官問你的分庫分表相關(guān)問題呀,如

分表方案(水平分表,垂直分表,切分規(guī)則hash等)
分庫分表中間件(Mycat,sharding-jdbc等)
分庫分表一些問題(事務(wù)問題?跨節(jié)點Join的問題)
解決方案(分布式事務(wù)等)
索引優(yōu)化
除了分庫分表,優(yōu)化表結(jié)構(gòu),當然還有所以索引優(yōu)化等方案~

  1. 如何寫sql能夠有效的使用到復(fù)合索引?
    復(fù)合索引,也叫組合索引,用戶可以在多個列上建立索引,這種索引叫做復(fù)合索引。

當我們創(chuàng)建一個組合索引的時候,如(k1,k2,k3),相當于創(chuàng)建了(k1)、(k1,k2)和(k1,k2,k3)三個索引,這就是最左匹配原則。

select * from table where k1=A AND k2=B AND k3=D
1
有關(guān)于復(fù)合索引,我們需要關(guān)注查詢Sql條件的順序,確保最左匹配原則有效,同時可以刪除不必要的冗余索引。

  1. mysql中in 和exists的區(qū)別
    假設(shè)表A表示某企業(yè)的員工表,表B表示部門表,查詢所有部門的所有員工,很容易有以下SQL:

select * from A where deptId in (select deptId from B);
1
這樣寫等價于:

先查詢部門表B select deptId from B 再由部門deptId,查詢A的員工 select * from A where A.deptId = B.deptId

可以抽象成這樣的一個循環(huán):

List<> resultSet ;
for(int i=0;i<B.length;i++) {
for(int j=0;j<A.length;j++) {
if(A[i].id==B[j].id) {
resultSet.add(A[i]);
break;
}
}
}
1
2
3
4
5
6
7
8
9
顯然,除了使用in,我們也可以用exists實現(xiàn)一樣的查詢功能,如下:

select * from A where exists (select 1 from B where A.deptId = B.deptId);
1
因為exists查詢的理解就是,先執(zhí)行主查詢,獲得數(shù)據(jù)后,再放到子查詢中做條件驗證,根據(jù)驗證結(jié)果(true或者false),來決定主查詢的數(shù)據(jù)結(jié)果是否得意保留。

那么,這樣寫就等價于:

select * from A,先從A表做循環(huán) select * from B where A.deptId = B.deptId,再從B表做循環(huán).

同理,可以抽象成這樣一個循環(huán):

List<> resultSet ;
for(int i=0;i<A.length;i++) {
for(int j=0;j<B.length;j++) {
if(A[i].deptId==B[j].deptId) {
resultSet.add(A[i]);
break;
}
}
}
1
2
3
4
5
6
7
8
9
數(shù)據(jù)庫最費勁的就是跟程序鏈接釋放。假設(shè)鏈接了兩次,每次做上百萬次的數(shù)據(jù)集查詢,查完就走,這樣就只做了兩次;相反建立了上百萬次鏈接,申請鏈接釋放反復(fù)重復(fù),這樣系統(tǒng)就受不了了。即mysql優(yōu)化原則,就是小表驅(qū)動大表,小的數(shù)據(jù)集驅(qū)動大的數(shù)據(jù)集,從而讓性能更優(yōu)。 因此,我們要選擇最外層循環(huán)小的,也就是,如果B的數(shù)據(jù)量小于A,適合使用in,如果B的數(shù)據(jù)量大于A,即適合選擇exists,這就是in和exists的區(qū)別。

  1. 數(shù)據(jù)庫自增主鍵可能遇到什么問題?
    使用自增主鍵對數(shù)據(jù)庫做分庫分表,可能出現(xiàn)諸如主鍵重復(fù)等的問題。解決方案的話,簡單點的話可以考慮使用UUID哈 自增主鍵會產(chǎn)生表鎖,從而引發(fā)問題 自增主鍵可能用完問題。

  2. MVCC底層原理
    我們聊下MySQL是如何實現(xiàn)Read Repeatable的吧,因為一般我們都不修改這個隔離級別,但是你得清楚是怎么回事兒,MySQL是通過MVCC機制來實現(xiàn)的,就是多版本并發(fā)控制,multi-version concurrency control。

innodb存儲引擎,會在每行數(shù)據(jù)的最后加兩個隱藏列,一個保存行的創(chuàng)建時間,一個保存行的刪除時間,但是這兒存放的不是時間,而是事務(wù)id,事務(wù)id是mysql自己維護的自增的,全局唯一。

事務(wù)id,在mysql內(nèi)部是全局唯一遞增的,事務(wù)id=1,事務(wù)id=2,事務(wù)id=3

id name 創(chuàng)建事務(wù)id 刪除事務(wù)id
1 張三 120 空
事務(wù)ID=121的事務(wù),查詢ID=1的這一行數(shù)據(jù),一定會找到創(chuàng)建事務(wù)ID<=當前事務(wù)ID的那一行,select * from table where id = 1,就可以查到上面那一行。

事務(wù)ID=122的事務(wù),將ID=1的這一行刪除了,此時就會將ID=1的行的刪除事務(wù)ID設(shè)置成122

id name 創(chuàng)建事務(wù)id 刪除事務(wù)id
1 張三 120 122
事務(wù)ID=121的事務(wù),再次查詢ID=1的那一行,能查到,創(chuàng)建事務(wù)ID<=當前事務(wù)ID,當前事務(wù)ID < 刪除事務(wù)ID

id name 創(chuàng)建事務(wù)id 刪除事務(wù)id
1 張三 120 122
2 李四 119 空
事務(wù)id=121的事務(wù),查詢id=2的那一行,查到name=李四

id name 創(chuàng)建事務(wù)id 刪除事務(wù)id
1 張三 120 122
2 李四 119 空
2 小李四 122 空
事務(wù)id=122的事務(wù),將id=2的那一行的name修改成name=小李四

Innodb存儲引擎,對于同一個ID,不同的事務(wù)創(chuàng)建或修改,每個事務(wù)都有自己的快照(會插入一條記錄)

事務(wù)id=121的事務(wù),查詢id=2的那一行,答案是:李四,創(chuàng)建事務(wù)id <= 當前事務(wù)id,當前事務(wù)id < 刪除事務(wù)id.

在一個事務(wù)內(nèi)查詢的時候,mysql只會查詢創(chuàng)建事務(wù)id <= 當前事務(wù)id的行,這樣可以確保這個行是在當前事務(wù)中創(chuàng)建,或者是之前創(chuàng)建的;同時一個行的刪除事務(wù)id要么沒有定義(就是沒刪除),要么是比當前事務(wù)id大(在事務(wù)開啟之后才被刪除);滿足這兩個條件的數(shù)據(jù)都會被查出來。

那么如果某個事務(wù)執(zhí)行期間,別的事務(wù)更新了一條數(shù)據(jù)呢?這個很關(guān)鍵的一個實現(xiàn),其實就是在innodb中,是插入了一行記錄,然后將新插入的記錄的創(chuàng)建時間設(shè)置為新的事務(wù)的id,同時將這條記錄之前的那個版本的刪除時間設(shè)置為新的事務(wù)的id。

現(xiàn)在get到這個點了吧?這樣的話,你的這個事務(wù)其實對某行記錄的查詢,始終都是查找的之前的那個快照,因為之前的那個快照的創(chuàng)建時間小于等于自己事務(wù)id,然后刪除時間的事務(wù)id比自己事務(wù)id大,所以這個事務(wù)運行期間,會一直讀取到這條數(shù)據(jù)的同一個版本。

  1. 數(shù)據(jù)庫中間件了解過嗎,sharding jdbc,mycat?
    sharding-jdbc目前是基于jdbc驅(qū)動,無需額外的proxy,因此也無需關(guān)注proxy本身的高可用。 Mycat 是基于 Proxy,它復(fù)寫了 MySQL 協(xié)議,將 Mycat Server 偽裝成一個 MySQL 數(shù)據(jù)庫,而 Sharding-JDBC 是基于 JDBC 接口的擴展,是以 jar 包的形式提供輕量級服務(wù)的。

  2. MySQL的主從延遲,你怎么解決?

主從復(fù)制分了五個步驟進行:
步驟一:主庫的更新事件(update、insert、delete)被寫到binlog
步驟二:從庫發(fā)起連接,連接到主庫。
步驟三:此時主庫創(chuàng)建一個binlog dump thread,把binlog的內(nèi)容發(fā)送到從庫。
步驟四:從庫啟動之后,創(chuàng)建一個I/O線程,讀取主庫傳過來的binlog內(nèi)容并寫入到relay log
步驟五:還會創(chuàng)建一個SQL線程,從relay log里面讀取內(nèi)容,從Exec_Master_Log_Pos位置開始執(zhí)行讀取到的更新事件,將更新內(nèi)容寫入到slave的db
主從同步延遲的原因
一個服務(wù)器開放N個鏈接給客戶端來連接的,這樣有會有大并發(fā)的更新操作, 但是從服務(wù)器的里面讀取binlog的線程僅有一個,當某個SQL在從服務(wù)器上執(zhí)行的時間稍長 或者由于某個SQL要進行鎖表就會導(dǎo)致,主服務(wù)器的SQL大量積壓,未被同步到從服務(wù)器里。這就導(dǎo)致了主從不一致, 也就是主從延遲。

主從同步延遲的解決辦法
可以參考沈劍老師的文章:數(shù)據(jù)庫主從不一致怎么解決?

  1. 說一下大表查詢的優(yōu)化方案
    優(yōu)化shema、sql語句+索引;
    可以考慮加緩存,memcached, redis,或者JVM本地緩存;
    主從復(fù)制,讀寫分離;
    分庫分表;
  2. 什么是數(shù)據(jù)庫連接池?為什么需要數(shù)據(jù)庫連接池呢?
    連接池基本原理:

數(shù)據(jù)庫連接池原理:在內(nèi)部對象池中,維護一定數(shù)量的數(shù)據(jù)庫連接,并對外暴露數(shù)據(jù)庫連接的獲取和返回方法。

應(yīng)用程序和數(shù)據(jù)庫建立連接的過程
通過TCP協(xié)議的三次握手和數(shù)據(jù)庫服務(wù)器建立連接
發(fā)送數(shù)據(jù)庫用戶賬號密碼,等待數(shù)據(jù)庫驗證用戶身份
完成身份驗證后,系統(tǒng)可以提交SQL語句到數(shù)據(jù)庫執(zhí)行
把連接關(guān)閉,TCP四次揮手告別。
數(shù)據(jù)庫連接池好處
資源重用 (連接復(fù)用)
更快的系統(tǒng)響應(yīng)速度
新的資源分配手段 統(tǒng)一的連接管理,避免數(shù)據(jù)庫連接泄漏

  1. 一條SQL語句在MySQL中如何執(zhí)行的?
    MySQL邏輯架構(gòu)圖
    MySQL分為Server層和存儲引擎層兩個部分,不同的存儲引擎共用一個Server層。

Server層:大多數(shù)MySQL的核心服務(wù)功能都在這一層,包括連接處理、授權(quán)認證、查詢解析、分析、優(yōu)化、緩存以及所有的內(nèi)置函數(shù)(例如,日期、時間、數(shù)學(xué)和加密函數(shù)),所有跨存儲引擎的功能都在這一層實現(xiàn),比如存儲過程、觸發(fā)器、視圖等。

存儲引擎層:存儲引擎負責MySQL中數(shù)據(jù)的存儲和提取。服務(wù)器通過API與存儲引擎進行通信。這些接口屏蔽了不同存儲引擎之間的差異,使得這些差異對上層的查詢過程透明。

MySQL客戶端與服務(wù)端的通信方式是“半雙工”,客戶端一旦開始發(fā)送消息另一端要接收完整這個消息才能響應(yīng),客戶端一旦開始接收數(shù)據(jù)就沒法停下來發(fā)送指令,一請求一響應(yīng)。

連接器
第一步,先連接到數(shù)據(jù)庫上,當客戶端(應(yīng)用)連接到MySQL服務(wù)器時,服務(wù)器需要對其進行認證,認證基于用戶名、原始主機信息和密碼,一旦客戶端連接成功,服務(wù)器會繼續(xù)驗證客戶端是否具有執(zhí)行某個特定查詢的權(quán)限(例如,是否允許客戶端對某一數(shù)據(jù)庫的某一表執(zhí)行SELECT語句)

連接命令:

mysql -hip -pport -u$user -p
1
輸完命令之后,需要在交互對話里面輸入密碼,密碼不建議在-p后面直接輸入,這樣會導(dǎo)致密碼泄露。

查詢緩存
第二步,查詢緩存,每次MySQL執(zhí)行過的語句及其結(jié)果會以key-value形式緩存在內(nèi)存中,key是查詢語句,value是查詢結(jié)果。如果查詢能夠在緩存中找到key,那么這個value就會被直接返回客戶端。

但是大多數(shù)情況下我會建議不要使用緩存,因為查詢緩存的失效非常頻繁,只要對一個表的更新,即便是更新一些與緩存無關(guān)的字段,這個表所有的緩存都會被清空,因此很可能會費勁地把結(jié)果存起來,還沒使用就被一個更新全部清空,對于更新壓力的數(shù)據(jù)庫來說,查詢緩存的命中率會非常低,除非業(yè)務(wù)就一張靜態(tài)表,很長時間才會更新一次。(例如系統(tǒng)配置表)

MySQL提供了按需使用的方式,可以將參數(shù)query_cache_type設(shè)置為DEMAND,這對于默認的SQL不使用查詢緩存,而對于確定要使用查詢緩存的語句,可以使用SQL_CACHE顯示指定。(SELECT SQL_CACHE * FROM TAB)

通過查詢語句做哈希算法得到一個哈希值,因此這里要想命中緩存,查詢SQL和緩存SQL必須完全一致,每次檢查緩存是否命中時都會對緩存加鎖,對于一個讀寫頻繁的系統(tǒng)使用查詢緩存很有可能降低查詢

注意:MySQL8.0版本直接將緩存的整個功能模塊刪掉了

分析器
第三步,分析器,如果沒有命中緩存,就會執(zhí)行SQL語句,首先讓MySQL知道我們需要做什么,因此需要對SQL語句解析,MySQL從輸入的“select”關(guān)鍵字識別出來,這是一條查詢語句,把字符串“TAB”識別成表名TAB,檢查查詢中涉及的表和數(shù)據(jù)列是否存在或別名是否有歧義

解析器的工作:語法分析(生成句子),語義分析(確保這些句子講得通),以及代碼生成(為編譯準備)

注意:分析器和解析器是一個東西,有些書叫分析器,有些書叫解析器,就是不同的叫法而已

優(yōu)化器
第四步,優(yōu)化器,經(jīng)過分析器MySQL知道我們需要什么了,在開始執(zhí)行前,還要經(jīng)過優(yōu)化器進行處理,優(yōu)化器是在表里面有多個索引時,決定使用哪個索引,或者在一個語句有多表關(guān)聯(lián)(join)時,決定各個表的連接順序。

優(yōu)化器會生成執(zhí)行計劃

執(zhí)行器
第五步,執(zhí)行器,MySQL通過分析器知道要做什么,通過優(yōu)化器知道怎么做,開始執(zhí)行前,要先判斷一下是否有表TABLE查詢權(quán)限,如果有打開表,根據(jù)表的引擎定義,去使用這個引擎提供的接口。

根據(jù)執(zhí)行計劃,調(diào)用存儲引擎API來查詢數(shù)據(jù)

  1. InnoDB引擎中的索引策略,了解過嗎?
    只有當索引幫助存儲引擎快速查找到記錄帶來的好處大于其帶來的額外工作時,索引才是有效的。對于非常小的表,大部分情況下簡單的全表掃描更高效,對于中到大型的表,索引就非常有效。

正確地創(chuàng)建和使用索引是實現(xiàn)高性能查詢的基礎(chǔ)。

獨立的列
如果查詢中的列不是獨立的,則MySQL就不會使用索引,’獨立的列’是指索引列不能是表達式的一部分,也不是函數(shù)的參數(shù)。

select actor_id from skill.actor where actor_id + 1 = 5,這個查詢無法使用actor_id列的索引;
select actor_id from skill.actor where to_days(current_date) - to_days(date_col) <= 10,這個也不會使用索引。

前綴索引和索引選擇性
有時候需要索引很長的字符列,這會讓索引變得大且慢,通??梢运饕_始的部分字符,這樣可以大大節(jié)約索引空間,從而提高索引效率,但這樣也會降低索引的選擇性,索引的選擇性是指,不重復(fù)的索引值和數(shù)據(jù)表的記錄總數(shù)(#T)的比值,范圍從1/#T到1之間,索引的選擇性越高則查詢效率越高,因為選擇性高的索引可以讓MySQL在查找時過濾掉更多的行,唯一索引的選擇性是1,這是最好的索引選擇性,性能也是最好的。

一般情況下某個列前綴的選擇性也是足夠高的,足以滿足查詢性能,對于BLOB、TEXT或者很長的VARCHAR類型的列,必須使用前綴索引,因為MySQL不允許索引這些列的完整程度。

訣竅在于要選擇足夠長的前綴以保證較高的選擇性,同時又不能太長(以便節(jié)約空間)。
如何選擇合適的前綴?

①我們可以通過left函數(shù)

②計算完整列的選擇性,并使前綴的選擇性接近于完整列的選擇性;
select count(distinct city)/count(*) from skill.city_demo,選擇性0.0312

select count(distinct left(city,6))/count(*) from skill.city_demo,選擇性0.0309

select count(distinct left(city,7))/count(*) from skill.city_demo,選擇性0.0310

前綴索引是一種能使索引更小、更快的有效方法,但另一方面也有其缺點:MySQL無法使用前綴索引做ORDER BY和GROUP BY,也無法使用前綴索引做覆蓋掃描。

有時候后綴索引也有用途,MySQL原聲不支持反向索引,但是可以把字符串反轉(zhuǎn)后存儲,并基于此建立前綴索引。

多列索引
一個常見的錯誤就是,為每個列創(chuàng)建一個獨立的索引或者按照錯誤的順序創(chuàng)建多列索引。

當出現(xiàn)服務(wù)器對多個索引做相交操作時(通常有多個AND條件),通常意味著需要一個包含所有相關(guān)列的多列索引,而不是多個獨立的單列索引。

當服務(wù)器需要對多個索引做聯(lián)合操作時(通常有多個OR條件),通常需要耗費大量CPU和內(nèi)存資源在算法的緩存、排序合并操作上,特別是當其中有些索引的選擇性不高,需要合并掃描返回的大量數(shù)據(jù)的時候,導(dǎo)致該執(zhí)行計劃還不如直接走全表掃描,這樣做不但會消耗更多的CPU和內(nèi)存資源,還可能會影響查詢的并發(fā)性。

單列索引:節(jié)點中關(guān)鍵字【name】

聯(lián)合索引:節(jié)點中關(guān)鍵字【name,phoneNum】

聯(lián)合索引列選擇原則:

①經(jīng)常用的列優(yōu)先【最左匹配原則】

②選擇性(離散性)高的優(yōu)先【離散度高原則】

③寬度小的列優(yōu)先【最少空間原則】

優(yōu)先級1>2>3

select * from t_user where name = ?

select * from t_user where name = ? and phoneNum = ?

create index index_name on t_user(name)

create index index_name_phoneNum on t_user(name,phoneNum)

這種做法是錯誤的,根據(jù)最左匹配原則,兩條查詢都可以走index_name_phoneNum索引,index_name索引就是冗余索引。

選擇合適的索引列順序
當不需要考慮排序和分組時,將選擇性最高的列放在前面通常是很好的,這時索引的作用只是用于優(yōu)化WHERE條件的查找,在這種情況下,這種設(shè)計的索引確實能夠最快地過濾出需要的行,對于在WHERE字句中只使用了索引部分前綴列的查詢來說選擇性也更高,然而,性能不只是依賴于所有索引列的選擇性,也和查詢條件的具體值有關(guān),也就是和值的分布有關(guān),可能需要根據(jù)那些運行頻率最高的查詢來調(diào)整索引列的順序,讓這種情況下索引的選擇性最高。

聚簇索引
聚簇索引并不是一種單獨的索引類型,而是一種數(shù)據(jù)存儲方式,具體的細節(jié)依賴于其實現(xiàn)方式,但InnoDB的聚簇索引實際上在同一個結(jié)構(gòu)中保存了B+Tree索引和數(shù)據(jù)行。

當表有聚簇索引時,它的數(shù)據(jù)行實際上存放在索引的葉子頁中,術(shù)語“聚簇”表示數(shù)據(jù)行和相鄰的鍵值緊湊地存儲在一起(這并非總成立),因為無法同時把數(shù)據(jù)行存在兩個不同的地方,所以一個表只能有一個聚簇索引(不過,覆蓋索引可以模擬多個聚簇索引的情況)。

因為是存儲引擎負責實現(xiàn)索引,因此不是所有的存儲引擎都支持聚簇索引,這里只關(guān)注InnoDB。

列的離散性
找出離散性好的列,離散性越高,可選擇性就越好。

例如:sex字段,只有男和女,離散性很差,因此選擇性很差

  1. 數(shù)據(jù)庫存儲日期格式時,如何考慮時區(qū)轉(zhuǎn)換問題?
    datetime類型適合用來記錄數(shù)據(jù)的原始的創(chuàng)建時間,修改記錄中其他字段的值,datetime字段的值不會改變,除非手動修改它。
    timestamp類型適合用來記錄數(shù)據(jù)的最后修改時間,只要修改了記錄中其他字段的值,timestamp字段的值都會被自動更新。
  2. 一條sql執(zhí)行過長的時間,你如何優(yōu)化,從哪些方面入手?
    查看是否涉及多表和子查詢,優(yōu)化Sql結(jié)構(gòu),如去除冗余字段,是否可拆表等
    優(yōu)化索引結(jié)構(gòu),看是否可以適當添加索引
    數(shù)量大的表,可以考慮進行分離/分表(如交易流水表)
    數(shù)據(jù)庫主從分離,讀寫分離
    explain分析sql語句,查看執(zhí)行計劃,優(yōu)化sql
    查看mysql執(zhí)行日志,分析是否有其他方面的問題
  3. Blob和text有什么區(qū)別?
    Blob用于存儲二進制數(shù)據(jù),而Text用于存儲大字符串。
    Blob值被視為二進制字符串(字節(jié)字符串),它們沒有字符集,并且排序和比較基于列值中的字節(jié)的數(shù)值。
    text值被視為非二進制字符串(字符字符串)。它們有一個字符集,并根據(jù)字符集的排序規(guī)則對值進行排序和比較。
  4. MySQL里記錄貨幣用什么字段類型比較好?
    貨幣在數(shù)據(jù)庫中MySQL常用Decimal和Numric類型表示,這兩種類型被MySQL實現(xiàn)為同樣的類型。他們被用于保存與金錢有關(guān)的數(shù)據(jù)。
    salary DECIMAL(9,2),9(precision)代表將被用于存儲值的總的小數(shù)位數(shù),而2(scale)代表將被用于存儲小數(shù)點后的位數(shù)。存儲在salary列中的值的范圍是從-9999999.99到9999999.99。
    DECIMAL和NUMERIC值作為字符串存儲,而不是作為二進制浮點數(shù),以便保存那些值的小數(shù)精度。
  5. InnoDB有哪幾種鎖?
    如何使用普通鎖保證一致性?

①操作數(shù)據(jù)前,加鎖,實施互斥,不允許其他的并發(fā)任務(wù)操作;

②操作完成后,釋放鎖,讓其他任務(wù)執(zhí)行;如此這般,來保證一致性。

普通鎖存在什么問題?

簡單的鎖住太過粗暴,連“讀任務(wù)”也無法并行,任務(wù)執(zhí)行過程本質(zhì)上是串行的。

共享/排它鎖(Shared and Exclusive Locks)
簡單的鎖住太過粗暴,連“讀任務(wù)”也無法并行,任務(wù)執(zhí)行過程本質(zhì)上是串行的。于是出現(xiàn)了共享鎖與排他鎖:

共享鎖(Share Locks,記為S鎖),讀取數(shù)據(jù)時加S鎖

排他鎖(eXclusive Locks,記為X鎖),修改數(shù)據(jù)時加X鎖

共享鎖與排他鎖:

共享鎖之間不互斥,讀讀可以并行

排他鎖與任何鎖互斥,寫讀,寫寫不可以并行

可以看到,一旦寫數(shù)據(jù)的任務(wù)沒有完成,數(shù)據(jù)是不能被其他任務(wù)讀取的,這對并發(fā)度有較大的影響。

有沒有可能,進一步提高并發(fā)呢?

即使寫任務(wù)沒有完成,其他讀任務(wù)也可能并發(fā),MySQL通過多版本控制解決此問題。(快照讀)

意向鎖(Intention Locks)
InnoDB支持多粒度鎖(multiple granularity locking),它允許行級鎖與表級鎖共存,實際應(yīng)用中,InnoDB使用的是意向鎖。

意向鎖是指,未來的某個時刻,事務(wù)可能要加共享/排它鎖了,先提前聲明一個意向。

意向鎖的特點:

①首先,意向鎖,是一個表級別的鎖(table-level locking);

②意向鎖分為:

意向共享鎖(intention shared lock, IS),它預(yù)示著,事務(wù)有意向?qū)Ρ碇械哪承┬屑庸蚕鞸鎖

意向排它鎖(intention exclusive lock, IX),它預(yù)示著,事務(wù)有意向?qū)Ρ碇械哪承┬屑优潘黊鎖

舉個例子:

select … lock in share mode,要設(shè)置IS鎖;

select … for update,要設(shè)置IX鎖;

③意向鎖協(xié)議(intention locking protocol)并不復(fù)雜:

事務(wù)要獲得某些行的S鎖,必須先獲得表的IS鎖

事務(wù)要獲得某些行的X鎖,必須先獲得表的IX鎖

④由于意向鎖僅僅表明意向,它其實是比較弱的鎖,意向鎖之間并不相互互斥,而是可以并行,其兼容互斥表如下:

IS IX
IS 兼容 兼容
IX 兼容 兼容
⑤既然意向鎖之間都相互兼容,那其意義在哪里呢?它會與共享鎖/排它鎖互斥,其兼容互斥表如下:

S X
IS 兼容 互斥
IX 互斥 互斥
補充:排它鎖是很強的鎖,不與其他類型的鎖兼容。這也很好理解,修改和刪除某一行的時候,必須獲得強鎖,禁止這一行上的其他并發(fā),以保障數(shù)據(jù)的一致性。

意向鎖解決什么問題?

事務(wù) A 獲取了某一行的排它鎖,并未提交: select * from table where id = 6 from update

事務(wù) B 想要獲取 table 表的表鎖: LOCK TABLES table READ;

因為共享鎖與排它鎖互斥,所以事務(wù) B 在視圖對 table 表加共享鎖的時候,必須保證:

①當前沒有其他事務(wù)持有 table 表的排它鎖。

②當前沒有其他事務(wù)持有 table 表中任意一行的排它鎖 。

為了檢測是否滿足第二個條件,事務(wù) B 必須在確保 table表不存在任何排它鎖的前提下,去檢測表中的每一行是否存在排它鎖。很明顯這是一個效率很差的做法,但是有了意向鎖之后,事務(wù)A持有了table表的意向排它鎖,就可得知事務(wù)A必然持有該表中某些數(shù)據(jù)行的排它鎖,而無需去檢測表中每一行是否存在排它鎖

意向鎖之間為什么互相兼容?

事務(wù) A 先獲取了某一行的排他鎖,并未提交: select * from users where id = 6 for update

①事務(wù) A 獲取了 users 表上的意向排他鎖。

②事務(wù) A 獲取了 id 為 6 的數(shù)據(jù)行上的排他鎖。

之后事務(wù) B 想要獲取 users 表的共享鎖: LOCK TABLES users READ;

事務(wù) B 檢測到事務(wù) A 持有 users 表的意向排他鎖。 事務(wù) B 對 users 表的加鎖請求被阻塞(排斥)。

最后事務(wù) C 也想獲取 users 表中某一行的排他鎖: select * from users where id = 5 for update;

①事務(wù) C 申請 users 表的意向排他鎖。

②事務(wù) C 檢測到事務(wù) A 持有 users 表的意向排他鎖。

③因為意向鎖之間并不互斥,所以事務(wù) C 獲取到了 users 表的意向排他鎖。

④因為id 為 5 的數(shù)據(jù)行上不存在任何排他鎖,最終事務(wù) C 成功獲取到了該數(shù)據(jù)行上的排他鎖。

如果意向鎖之間互斥,行級鎖的意義將會失去

記錄鎖(Record Locks)
記錄鎖,它封鎖索引記錄,例如:

select * from t where id=1 for update; 它會在id=1的索引記錄上加鎖,以阻止其他事務(wù)插入,更新,刪除id=1的這一行。

需要說明的是: select * from t where id=1; 則是快照讀(SnapShot Read),它并不加鎖,具體在《17.什么是快照讀?》中做了詳細闡述。

間隙鎖(Gap Locks)
間隙鎖,它封鎖索引記錄中的間隔,或者第一條索引記錄之前的范圍,又或者最后一條索引記錄之后的范圍。

存儲引擎:InnoDB

隔離級別:可重復(fù)讀隔離級別

建表語句:

mysql> CREATE TABLE T (
id int(11) NOT NULL,
name varchar(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE = INNODB;
1
2
3
4
5
數(shù)據(jù)列表:

id name
1 xiaohong
3 zhangsan
5 lisi
9 wangwu
這個SQL語句 select * from T where id between 8 and 15 for update; 會封鎖區(qū)間,以阻止其他事務(wù)id=10的記錄插入。

為什么要阻止id=10的記錄插入? 如果能夠插入成功,頭一個事務(wù)執(zhí)行相同的SQL語句,會發(fā)現(xiàn)結(jié)果集多出了一條記錄,即幻影數(shù)據(jù)。

間隙鎖的主要目的,就是為了防止其他事務(wù)在間隔中插入數(shù)據(jù),以導(dǎo)致“不可重復(fù)讀”。

如果把事務(wù)的隔離級別降級為讀提交(Read Committed, RC),間隙鎖則會自動失效。

臨鍵鎖(Next-key Locks)
臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖范圍,既包含索引記錄,又包含索引區(qū)間。

更具體的,臨鍵鎖會封鎖索引記錄本身,以及索引記錄之前的區(qū)間。

如果一個會話占有了索引記錄R的共享/排他鎖,其他會話不能立刻在R之前的區(qū)間插入新的索引記錄。

存儲引擎:InnoDB

隔離級別:可重復(fù)讀隔離級別

建表語句:

mysql> CREATE TABLE T (
id int(11) NOT NULL,
name varchar(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE = INNODB;
1
2
3
4
5
數(shù)據(jù)列表:

id name
1 xiaohong
3 zhangsan
5 lisi
9 wangwu
PK上潛在的臨鍵鎖為:

(-infinity, 1]

(1, 3]

(3, 5]

(5, 9]

(9, +infinity]

臨鍵鎖的主要目的,也是 為了避免幻讀(Phantom Read) 。如果把事務(wù)的隔離級別降級為RC,臨鍵鎖則也會失效。

插入意向鎖(Insert Intention Locks)
對已有數(shù)據(jù)行的修改與刪除,必須加強互斥鎖X鎖,那對于數(shù)據(jù)的插入,是否還需要加這么強的鎖,來實施互斥呢?插入意向鎖,孕育而生。

插入意向鎖,是間隙鎖(Gap Locks)的一種(所以,也是實施在索引上的),它是專門針對insert操作的。

多個事務(wù),在同一個索引,同一個范圍區(qū)間插入記錄時,如果插入的位置不沖突,不會阻塞彼此。

存儲引擎:InnoDB

隔離級別:可重復(fù)讀隔離級別

建表語句:

mysql> CREATE TABLE T (
id int(11) NOT NULL,
name varchar(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE = INNODB;
1
2
3
4
5
數(shù)據(jù)列表:

id Name
10 xiaohong
20 zhangsan
30 lisi
事務(wù)A先執(zhí)行,在10與20兩條記錄中插入了一行,還未提交:

insert into t values(11, xxx);

事務(wù)B后執(zhí)行,也在10與20兩條記錄中插入了一行:

insert into t values(12, ooo);

會使用什么鎖?事務(wù)B會不會被阻塞呢? 回答:雖然事務(wù)隔離級別是RR,雖然是同一個索引,雖然是同一個區(qū)間,但插入的記錄并不沖突,故這里: 使用的是插入意向鎖,并不會阻塞事務(wù)B

自增鎖(Auto-inc Locks)
案例說明:

存儲引擎:InnoDB

隔離級別:可重復(fù)讀隔離級別

建表語句:

mysql> CREATE TABLE T (
id int(11) NOT NULL,
name varchar(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE = INNODB;
1
2
3
4
5
數(shù)據(jù)列表:

id name
1 xiaohong
2 zhangsan
3 lisi
事務(wù)A先執(zhí)行,還未提交: insert into t(name) values(xxx);

事務(wù)B后執(zhí)行: insert into t(name) values(ooo);

事務(wù)B會不會被阻塞?

案例分析:

InnoDB在RR隔離級別下,能解決幻讀問題,上面這個案例中:

①事務(wù)A先執(zhí)行insert,會得到一條(4, xxx)的記錄,由于是自增列,故不用顯示指定id為4,InnoDB會自動增長,注意此時事務(wù)并未提交;

②事務(wù)B后執(zhí)行insert,假設(shè)不會被阻塞,那會得到一條(5, ooo)的記錄;

此時,并未有什么不妥,但如果,

③事務(wù)A繼續(xù)insert:

insert into t(name) values(xxoo);

會得到一條(6, xxoo)的記錄。

④事務(wù)A再select:

select * from t where id>3;

得到的結(jié)果是:

4, xxx

6, xxoo

補充:不可能查詢到5的記錄,再RR的隔離級別下,不可能讀取到還未提交事務(wù)生成的數(shù)據(jù)。

這對于事務(wù)A來說,就很奇怪了,對于AUTO_INCREMENT的列,連續(xù)插入了兩條記錄,一條是4,接下來一條變成了6,就像莫名其妙的幻影。

自增鎖是一種特殊的表級別鎖(table-level lock),專門針對事務(wù)插入AUTO_INCREMENT類型的列。最簡單的情況,如果一個事務(wù)正在往表中插入記錄,所有其他事務(wù)的插入必須等待,以便第一個事務(wù)插入的行,是連續(xù)的主鍵值。

與此同時,InnoDB提供了innodb_autoinc_lock_mode配置,可以調(diào)節(jié)與改變該鎖的模式與行為。

  1. Hash索引和B+樹區(qū)別是什么?你在設(shè)計索引是怎么抉擇的?
    B+樹可以進行范圍查詢,Hash索引不能。
    B+樹支持聯(lián)合索引的最左側(cè)原則,Hash索引不支持。
    B+樹支持order by排序,Hash索引不支持。
    Hash索引在等值查詢上比B+樹效率更高。
    B+樹使用like 進行模糊查詢的時候,like后面(比如%開頭)的話可以起到優(yōu)化的作用,Hash索引根本無法進行模糊查詢。
  2. mysql 的內(nèi)連接、左連接、右連接有什么區(qū)別?
    Inner join 內(nèi)連接,在兩張表進行連接查詢時,只保留兩張表中完全匹配的結(jié)果集
    left join 在兩張表進行連接查詢時,會返回左表所有的行,即使在右表中沒有匹配的記錄。
    right join 在兩張表進行連接查詢時,會返回右表所有的行,即使在左表中沒有匹配的記錄。
  3. 什么是內(nèi)連接、外連接、交叉連接、笛卡爾積呢?
    內(nèi)連接(inner join):取得兩張表中滿足存在連接匹配關(guān)系的記錄。
    外連接(outer join):取得兩張表中滿足存在連接匹配關(guān)系的記錄,以及某張表(或兩張表)中不滿足匹配關(guān)系的記錄。
    交叉連接(cross join):顯示兩張表所有記錄一一對應(yīng),沒有匹配關(guān)系進行篩選,也被稱為:笛卡爾積。
  4. 說一下數(shù)據(jù)庫的三大范式
    第一范式:數(shù)據(jù)表中的每一列(每個字段)都不可以再拆分。
    第二范式:在第一范式的基礎(chǔ)上,分主鍵列完全依賴于主鍵,而不能是依賴于主鍵的一部分。
    第三范式:在滿足第二范式的基礎(chǔ)上,表中的非主鍵只依賴于主鍵,而不依賴于其他非主鍵。
  5. mysql有關(guān)權(quán)限的表有哪幾個呢?
    MySQL服務(wù)器通過權(quán)限表來控制用戶對數(shù)據(jù)庫的訪問,權(quán)限表存放在mysql數(shù)據(jù)庫里,由mysql_install_db腳本初始化。這些權(quán)限表分別user,db,table_priv,columns_priv和host。

user權(quán)限表:記錄允許連接到服務(wù)器的用戶帳號信息,里面的權(quán)限是全局級的。
db權(quán)限表:記錄各個帳號在各個數(shù)據(jù)庫上的操作權(quán)限。
table_priv權(quán)限表:記錄數(shù)據(jù)表級的操作權(quán)限。
columns_priv權(quán)限表:記錄數(shù)據(jù)列級的操作權(quán)限。
host權(quán)限表:配合db權(quán)限表對給定主機上數(shù)據(jù)庫級操作權(quán)限作更細致的控制。這個權(quán)限表不受GRANT和REVOKE語句的影響。
主從復(fù)制binlog格式有哪幾種?有什么區(qū)別?
①STATEMENT,基于語句的日志記錄,把所有寫操作的sql語句寫入 binlog (默認)
例如update xxx set update_time = now() where pk_id = 1,這時,主從的 update_time 不一致
優(yōu)點:
成熟的技術(shù)。
更少的數(shù)據(jù)寫入日志文件。當更新或刪除影響許多行時,這將導(dǎo)致 日志文件所需的存儲空間大大減少。這也意味著從備份中獲取和還原可以更快地完成。
日志文件包含所有進行了任何更改的語句,因此它們可用于審核數(shù)據(jù)庫。

缺點:
有很多函數(shù)不能復(fù)制,例如now()、random()、uuid()等

②ROW,基于行的日志記錄,把每一行的改變寫入binlog,假設(shè)一條sql語句影響100萬行,從節(jié)點需要執(zhí)行100萬次,效率低。
優(yōu)點:可以復(fù)制所有更改,這是最安全的復(fù)制形式
缺點:如果該SQL語句更改了許多行,則基于行的復(fù)制可能會向二進制日志中寫入更多的數(shù)據(jù)。即使對于回滾的語句也是如此。這也意味著制作和還原備份可能需要更多時間。此外,二進制日志被鎖定更長的時間以寫入數(shù)據(jù),這可能會導(dǎo)致并發(fā)問題。

③MIXED,混合模式,如果 sql 里有函數(shù),自動切換到 ROW 模式,如果 sql 里沒有會造成主從復(fù)制不一致的函數(shù),那么就使用STATEMENT模式。(存在問題:解決不了系統(tǒng)變量問題,例如@@host name,主從的主機名不一致)

  1. Mysql主從復(fù)制方式?有什么區(qū)別?
    ①異步復(fù)制
    網(wǎng)絡(luò)或機器故障時,會造成數(shù)據(jù)不一致

數(shù)據(jù)不一致緩解方案:半同步,插入主庫時,不會及時返回給我們的web端,他會進行等待,等待從庫的I/OThread從主節(jié)點Binary log讀取二進制文件并拷貝到從節(jié)點的relaybinlog之后,在進行返回。(不是等待所有,一個從節(jié)點復(fù)制過去就行了)

數(shù)據(jù)強一致性了但是性能低:可以設(shè)置超時時間(多個Slave,或者Slave非???,會導(dǎo)致響應(yīng)非常慢?不會,有保護機制,超過時間就直接返回,一般情況下設(shè)置1秒)

注意:不是等待所有從節(jié)點同步從主節(jié)點Binary log讀取二進制文件并拷貝到從節(jié)點的relaybinlog之后才返回,而是只要有一個節(jié)點拷貝成功就返回

根據(jù)業(yè)務(wù)場景選擇同步和半同步

注意:半同步只會緩解數(shù)據(jù)不一致問題,并不能完全解決

②半同步復(fù)制(MySQL 8.0還支持通過插件實現(xiàn)的半同步復(fù)制接口)

默認情況下,MySQL復(fù)制是異步的。Master將事件寫入其二進制日志,Slave將在事件就緒時請求它們。Master不知道Slave是否或何時檢索和處理了事務(wù),并且不能保證任何事件都會到達Slave。使用異步復(fù)制,如果Master崩潰,則它提交的事務(wù)可能不會傳輸?shù)饺魏蜸lave。在這種情況下,從Master到Slave的故障轉(zhuǎn)移可能會導(dǎo)致故障轉(zhuǎn)移到缺少相對于Master的事務(wù)的服務(wù)器。

在完全同步復(fù)制的情況下,當Master提交事務(wù)時,所有Slave也都已提交事務(wù),然后Master才返回執(zhí)行該事務(wù)的會話。完全同步復(fù)制意味著可以隨時從Master故障轉(zhuǎn)移到任何Slave。完全同步復(fù)制的缺點是完成事務(wù)可能會有很多延遲。

半同步復(fù)制介于異步復(fù)制和完全同步復(fù)制之間。Master等待直到至少一個Slave接收并記錄了事件(所需數(shù)量的Slave是可配置的),然后提交事務(wù)。Master不等待所有Slave都確認收到,它僅需要Slave的確認,而不是事件已在Slave端完全執(zhí)行并提交。因此,半同步復(fù)制可確保如果Master崩潰,則它已提交的所有事務(wù)都已傳輸?shù)街辽僖粋€Slave。

與異步復(fù)制相比,半同步復(fù)制提供了改進的數(shù)據(jù)完整性,因為眾所周知,當提交成功返回時,數(shù)據(jù)至少存在兩個位置。在半同步Master收到所需數(shù)量的Slave的確認之前,該事務(wù)處于暫掛狀態(tài)且未提交。

與完全同步復(fù)制相比,半同步復(fù)制更快,因為半同步復(fù)制可以配置為平衡對數(shù)據(jù)完整性(確認已收到事務(wù)的Slave數(shù))與提交速度的需求,提交速度較慢,因為需要等待Slave。

與異步復(fù)制相比,半同步復(fù)制對性能的影響是增加數(shù)據(jù)完整性的權(quán)衡。減慢量至少是將提交發(fā)送到Slave并等待Slave確認接收的TCP / IP往返時間。這意味著半同步復(fù)制最適合通過快速網(wǎng)絡(luò)通信的關(guān)閉服務(wù)器,而最不適合通過慢速網(wǎng)絡(luò)通信的遠程服務(wù)器。半同步復(fù)制還通過限制二進制日志事件從Master發(fā)送到Slave的速度,對繁忙的會話設(shè)置了速率限制。當一個用戶太忙時,這會減慢速度,這在某些部署情況下很有用。

Master及其Slave之間的半同步復(fù)制操作如下:
Slave表示連接到Master時是否具有半同步功能。
如果在Master端啟用了半同步復(fù)制,并且至少有一個半同步Slave,則在Master塊上執(zhí)行事務(wù)提交的線程將等待直到至少一個半同步Slave確認已接收到該事務(wù)的所有事件,或者直到發(fā)生超時。
僅在事件已被寫入其中繼日志并刷新到磁盤之后,Slave才確認接收到事務(wù)事件。
如果在沒有任何Slave確認事務(wù)的情況下發(fā)生超時,則Master將恢復(fù)為異步復(fù)制。趕上至少一個半同步Slave時,Master將返回到半同步復(fù)制。
必須在Master端和Slave端都啟用半同步復(fù)制。如果在Master上禁用了半同步復(fù)制,或者在Master上啟用了半同步復(fù)制但沒有任何Slave,則Master使用異步復(fù)制。

③延遲復(fù)制
MySQL 8.0還支持延遲復(fù)制,以使副本故意在源之后至少指定的時間量

  1. InnoDB內(nèi)存結(jié)構(gòu)包含四大核心組件
    緩沖池(Buffer Pool),可以參考沈健老師文章緩沖池(buffer pool),這次徹底懂了?。。?br> 寫緩沖(Change Buffer),可以參考沈健老師文章寫緩沖(change buffer),這次徹底懂了?。?!
    自適應(yīng)哈希索引(Adaptive Hash Index),可以參考沈健老師文章自適應(yīng)哈希索引
    日志緩沖(Log Buffer),可以參考沈健老師文章事務(wù)已提交,數(shù)據(jù)卻丟了,趕緊檢查下這個配置?。?! | 數(shù)據(jù)庫系列
  2. 索引有哪些優(yōu)缺點?
    優(yōu)點
    唯一索引可以保證數(shù)據(jù)庫表中每一行的數(shù)據(jù)的唯一性
    索引可以加快數(shù)據(jù)查詢速度,減少查詢時間
    缺點
    創(chuàng)建索引和維護索引要耗費時間
    索引需要占物理空間,除了數(shù)據(jù)表占用數(shù)據(jù)空間之外,每一個索引還要占用一定的物理空間
    以表中的數(shù)據(jù)進行增、刪、改的時候,索引也要動態(tài)的維護。
  3. 索引有哪幾種類型?
    主鍵索引: 數(shù)據(jù)列不允許重復(fù),不允許為NULL,一個表只能有一個主鍵。
    唯一索引: 數(shù)據(jù)列不允許重復(fù),允許為NULL值,一個表允許多個列創(chuàng)建唯一索引。
    普通索引: 基本的索引類型,沒有唯一性的限制,允許為NULL值。
    全文索引:是目前搜索引擎使用的一種關(guān)鍵技術(shù),對文本的內(nèi)容進行分詞、搜索。
    覆蓋索引:查詢列要被所建的索引覆蓋,不必讀取數(shù)據(jù)行
    組合索引:多列值組成一個索引,用于組合搜索,效率大于索引合并
  4. 創(chuàng)建索引的三種方式
    在執(zhí)行CREATE TABLE時創(chuàng)建索引
    CREATE TABLE employee (
    id int(11) NOT NULL,
    name varchar(255) DEFAULT NULL,
    age int(11) DEFAULT NULL,
    date datetime DEFAULT NULL,
    sex int(1) DEFAULT NULL,
    PRIMARY KEY (id),
    KEY idx_name (name) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    使用ALTER TABLE命令添加索引
    ALTER TABLE table_name ADD INDEX index_name (column);
    1
    使用CREATE INDEX命令創(chuàng)建
    CREATE INDEX index_name ON table_name (column);
    1
  5. 百萬級別或以上的數(shù)據(jù),你是如何刪除的?
    我們想要刪除百萬數(shù)據(jù)的時候可以先刪除索引
    然后批量刪除其中無用數(shù)據(jù)
    刪除完成后重新創(chuàng)建索引。
  6. 覆蓋索引、回表等這些,了解過嗎?
    覆蓋索引: 查詢列要被所建的索引覆蓋,不必從數(shù)據(jù)表中讀取,換句話說查詢列要被所使用的索引覆蓋。
    回表:二級索引無法直接查詢所有列的數(shù)據(jù),所以通過二級索引查詢到聚簇索引后,再查詢到想要的數(shù)據(jù),這種通過二級索引查詢出來的過程,就叫做回表。
  7. B+樹在滿足聚簇索引和覆蓋索引的時候不需要回表查詢數(shù)據(jù)?
    在B+樹的索引中,葉子節(jié)點可能存儲了當前的key值,也可能存儲了當前的key值以及整行的數(shù)據(jù),這就是聚簇索引和非聚簇索引。 在InnoDB中,只有主鍵索引是聚簇索引,如果沒有主鍵,則挑選一個唯一鍵建立聚簇索引。如果沒有唯一鍵,則隱式的生成一個鍵來建立聚簇索引。
    當查詢使用聚簇索引時,在對應(yīng)的葉子節(jié)點,可以獲取到整行數(shù)據(jù),因此不用再次進行回表查詢。
  8. 何時使用聚簇索引與非聚簇索引
  1. 非聚簇索引一定會回表查詢嗎?
    不一定,如果查詢語句的字段全部命中了索引,那么就不必再進行回表查詢(哈哈,覆蓋索引就是這么回事)。

舉個簡單的例子,假設(shè)我們在學(xué)生表的上建立了索引,那么當進行select age from student where age < 20的查詢時,在索引的葉子節(jié)點上,已經(jīng)包含了age信息,不會再次進行回表查詢。

  1. 組合索引是什么?為什么需要注意組合索引中的順序?
    組合索引,用戶可以在多個列上建立索引,這種索引叫做組合索引。 因為InnoDB引擎中的索引策略的最左原則,所以需要注意組合索引中的順序。

  2. 什么是死鎖?怎么解決?
    死鎖是指兩個或多個事務(wù)在同一資源上相互占用,并請求鎖定對方的資源,從而導(dǎo)致惡性循環(huán)的現(xiàn)象??磮D形象一點,如下:

死鎖有四個必要條件:互斥條件,請求和保持條件,環(huán)路等待條件,不剝奪條件。 解決死鎖思路,一般就是切斷環(huán)路,盡量避免并發(fā)形成環(huán)路。

如果不同程序會并發(fā)存取多個表,盡量約定以相同的順序訪問表,可以大大降低死鎖機會。
在同一個事務(wù)中,盡可能做到一次鎖定所需要的所有資源,減少死鎖產(chǎn)生概率;
對于非常容易產(chǎn)生死鎖的業(yè)務(wù)部分,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產(chǎn)生的概率;
如果業(yè)務(wù)處理不好可以用分布式事務(wù)鎖或者使用樂觀鎖
死鎖與索引密不可分,解決索引問題,需要合理優(yōu)化你的索引,

  1. 你是如何監(jiān)控你們的數(shù)據(jù)庫的?你們的慢日志都是怎么查詢的?
    監(jiān)控的工具有很多,例如zabbix,lepus,我這里用的是lepus
    ————————————————
    版權(quán)聲明:本文為CSDN博主「Java程序魚」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
    原文鏈接:https://blog.csdn.net/qq_35620342/article/details/119930887
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容