MySQL11種核心底層原理

mysql性能的處理方式:

遺忘的概念:

一、Mysql的結(jié)構(gòu)

1.mysql中數(shù)據(jù)庫(kù)與數(shù)據(jù)庫(kù)實(shí)例的區(qū)別

2.mysql數(shù)據(jù)庫(kù)的組成

3.innodb的介紹

3.1innodb的架構(gòu)

二、sql的執(zhí)行過(guò)程

2.1查詢(xún)sql的執(zhí)行過(guò)程

2.2修改sql的執(zhí)行過(guò)程

2.3change buffer

2.4查詢(xún)中的排序算法(臨時(shí)表)

三、數(shù)據(jù)庫(kù)的事務(wù)

四、索引

五、數(shù)據(jù)庫(kù)鎖

六、表空間

七、性能分析

八、主備一致和讀寫(xiě)分離

九、join語(yǔ)句的執(zhí)行過(guò)程

十、臨時(shí)表的解釋

十一、分表

mysql性能的處理方式:

異步(日志記錄),備份(從庫(kù)),版本號(hào)(事務(wù)),標(biāo)識(shí)(鎖),多線程(鏈接池),批量處理(批量排序按索引查詢(xún)),內(nèi)存(各種buffer),排序(索引),盡可能的記錄標(biāo)識(shí)(異常重啟之后根據(jù)標(biāo)識(shí)判斷分情況還原處理)

遺忘的概念:

1.鎖

全局鎖:是給整個(gè)庫(kù)加鎖

表鎖:給整個(gè)表加鎖

行鎖:給掃描到的所有行加鎖(一個(gè)沒(méi)有索引的update會(huì)給整個(gè)表現(xiàn)有的行加鎖)

行鎖加鎖和釋放的原則:

? ? ? 讀已提交隔離級(jí)別下

? ? ? select * from t where d = 5 for update (d列沒(méi)索引)(就算d列有索引根據(jù)掃描的過(guò)程也可能掃描到多行)

? ? ? sql執(zhí)行過(guò)程中:掃描全表,這時(shí)掃描到的行都加鎖 掃描到的間歇也加間歇鎖 就是next_key lock

? ? ? sql執(zhí)行完之后:不滿(mǎn)足條件的行 d != 5 的行 next_key lock都會(huì)被去掉

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? d = 5 的行還是得等到事務(wù)提交時(shí)行鎖才釋放

間歇鎖:給掃描到的索引區(qū)間加鎖

行鎖用來(lái)鎖定涉及到的行(保證一致性),間歇鎖用來(lái)鎖定涉及到的區(qū)間(防止幻讀)可以根據(jù)業(yè)務(wù)場(chǎng)景反推為什么這么用鎖

2.mvcc處理事務(wù),是根據(jù)版本號(hào)對(duì)比處理,不一樣的事務(wù)加版本號(hào)的時(shí)機(jī)不一樣

3.事務(wù)的隔離級(jí)別是就當(dāng)前事務(wù)而言的

4. MVCC控制事務(wù)的讀取的可見(jiàn)性,鎖是用來(lái)控制讀寫(xiě)造成的沖突,鎖和mvcc共同實(shí)現(xiàn)事務(wù)的效果

5.索引是一種數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)行都在主鍵索引下邊,普通索引下是主鍵id

數(shù)據(jù)行 數(shù)據(jù)頁(yè)的概念

6.可重復(fù)讀級(jí)別下會(huì)觸發(fā)"間歇鎖"

7.只有當(dāng)前讀才有幻讀的問(wèn)題? 有了間歇鎖當(dāng)前讀也不會(huì)出現(xiàn)幻讀 因?yàn)閿?shù)據(jù)insert不了

普通的讀mvcc就能解決,當(dāng)前讀需要next_key lock解決

8.可重復(fù)讀就解決了幻度,串行化是通過(guò)表鎖解決的

三范式? 表設(shè)計(jì)? 字段屬性選用? 索引設(shè)計(jì)

了解HBASE

分庫(kù) 分表的利弊

一、Mysql的結(jié)構(gòu)

1.mysql中數(shù)據(jù)庫(kù)與數(shù)據(jù)庫(kù)實(shí)例的區(qū)別

數(shù)據(jù)庫(kù)是文件的集合,是依照某種數(shù)據(jù)模型組織起來(lái)存放于二級(jí)存儲(chǔ)器中的數(shù)據(jù)的集合

數(shù)據(jù)庫(kù)實(shí)例是程序,所有對(duì)數(shù)據(jù)庫(kù)的操作必須在數(shù)據(jù)庫(kù)實(shí)例下進(jìn)行

2.mysql數(shù)據(jù)庫(kù)的組成

連接池、管理服務(wù)和工具的組件、sql接口組件、查詢(xún)分析器、優(yōu)化器、緩沖組件、存儲(chǔ)引擎、物理文件

存儲(chǔ)引擎:是基于底層物理結(jié)構(gòu)的實(shí)現(xiàn),是基于表的不是數(shù)據(jù)庫(kù)

3.innodb的介紹

特點(diǎn):支持行鎖設(shè)計(jì)、mvcc、外鍵、提供一致性非鎖定讀、能有效利用內(nèi)存和cpu

3.1innodb的架構(gòu)

包括:多個(gè)后臺(tái)線程、和內(nèi)存池(緩存)

后臺(tái)線程保證內(nèi)存池中的數(shù)據(jù)和緩存中的數(shù)據(jù)一致,將緩存中修改過(guò)的數(shù)據(jù)刷新到數(shù)據(jù)庫(kù),同時(shí)將數(shù)據(jù)最新的數(shù)據(jù)同步到緩存

3.1.1后臺(tái)線程

Master Thread 將緩存池中的數(shù)據(jù)異步刷新到磁盤(pán),保證數(shù)據(jù)一致性

IO Thread innodb中大量使用AIO來(lái)操作數(shù)據(jù)庫(kù)讀寫(xiě),write、read、insertBuffer、logThread

Purge Thread

3.1.2innodb內(nèi)存

innodb的緩存

為了彌補(bǔ)磁盤(pán)操作較慢的問(wèn)題,innodb對(duì)數(shù)據(jù)庫(kù)加了一步緩存操作

1.讀取數(shù)據(jù),在數(shù)據(jù)庫(kù)讀取頁(yè)的操作時(shí),首先將從磁盤(pán)讀到的頁(yè)放到緩存池當(dāng)中,這個(gè)過(guò)程稱(chēng)將頁(yè)“FIX”在緩存池中,下次讀取相同的頁(yè)時(shí)先判斷該頁(yè)是否在緩存池中。若在緩存池中則命中直接讀取該頁(yè),若沒(méi)有則從磁盤(pán)中讀取。

2.修改數(shù)據(jù),首先修改在緩存池中的頁(yè),然后再以一定的頻率刷到磁盤(pán)上,不是每次更新都會(huì)觸發(fā)緩存和磁盤(pán)的同步,而是通過(guò)一種Checkpoint的機(jī)制。

連接池

HikariCP:目前速度最快的連接池,優(yōu)化了代理和攔截器,簡(jiǎn)化了代碼 新增了fastList,適合做監(jiān)控

Durid:經(jīng)過(guò)阿里大規(guī)模數(shù)據(jù)的檢驗(yàn)比較可靠

DBCP/C3P0速度比價(jià)慢

按照效率排序的話,count(字段)<count(主鍵 id)<count(1)≈count(*)

二、sql的執(zhí)行過(guò)程

2.1查詢(xún)sql的執(zhí)行過(guò)程

連接器(連接數(shù)據(jù)庫(kù))、(緩存執(zhí)行請(qǐng)求先走緩存(8.0之前))、分析器(分析請(qǐng)求類(lèi)型、表、列 檢查sql的正確性)、優(yōu)化器(選擇高效率的執(zhí)行策略)、執(zhí)行器(調(diào)用存儲(chǔ)引擎的接口獲取數(shù)據(jù))

2.2修改sql的執(zhí)行過(guò)程

update T set c=c+1 where id = 2? 數(shù)據(jù)表使用了innodb

同樣是經(jīng)歷 連接器 緩存 分析器 優(yōu)化器 執(zhí)行器

先從緩存或磁盤(pán)中取出id=2的數(shù)據(jù),讓后將取出的c的值加一

將修改后的這條數(shù)據(jù)更新到innodb的內(nèi)存中同時(shí)將更新的內(nèi)容更新到redo日志中,此時(shí)redo是prepare狀態(tài),然后告知執(zhí)行器執(zhí)行完了,隨時(shí)可以提交事務(wù)

然后執(zhí)行器生成這波操作的binlog日志),當(dāng)當(dāng)前這個(gè)事務(wù)提交后redo log和binlog同時(shí)完成寫(xiě)入

這里redo log 和 binlog是分兩階段提交的,先是準(zhǔn)備后是提交,兩個(gè)日志要么都寫(xiě)入成功要么都寫(xiě)入不成功

redo log 和 binlog的區(qū)別:

redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實(shí)現(xiàn)的,所有引擎都可以使用。

redo log是物理日志(保證事務(wù)的持久性),記錄的是“在某個(gè)數(shù)據(jù)頁(yè)上做了什么修改”;binlog 是邏輯日志,記錄的是這個(gè)語(yǔ)句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。

redo log 是循環(huán)寫(xiě)的,空間固定會(huì)用完;binlog 是可以追加寫(xiě)入的。“追加寫(xiě)”是指 binlog 文件寫(xiě)到一定大小后會(huì)切換到下一個(gè),并不會(huì)覆蓋以前的日志。

Binlog有兩種模式,statement 格式的話是記sql語(yǔ)句, row格式會(huì)記錄行的內(nèi)容,記兩條,更新前和更新后都有

測(cè)試:從redo log功能角度解釋一下innodb的緩存

check point 的觸發(fā)時(shí)機(jī):redo log 要寫(xiě)滿(mǎn)了、緩存不足、空閑時(shí)間、MySQL關(guān)閉

2.3change buffer

使用場(chǎng)景:

1.當(dāng)要修改一條數(shù)據(jù),

2.這條數(shù)據(jù)在內(nèi)存中不存在

3.先將修改的操作記錄在change buffer中

4.當(dāng)再次讀取這條數(shù)據(jù)時(shí),先將數(shù)據(jù)讀取到內(nèi)存中再用change buffer中記錄的邏輯來(lái)修改內(nèi)存中的數(shù)據(jù)

如果內(nèi)存中存在就直接修改內(nèi)存了? 唯一索引因?yàn)楸仨氁x取數(shù)據(jù)判斷數(shù)據(jù)是否沖突所以不適用這種場(chǎng)景,change buffer 就是為了減少磁盤(pán)的讀取io? rodo log是為了減少寫(xiě)io

思考問(wèn)題點(diǎn):

1.innodb中 緩存,change buffer,redo log ,磁盤(pán)的關(guān)系

2.change buffer的使用場(chǎng)景(普通索引,寫(xiě)操作大于讀操作)


2.4查詢(xún)中的排序算法(臨時(shí)表)

核心:通過(guò)sort_buffer將order by的列存起來(lái)然后做排序 再根據(jù)排序好的列來(lái)取數(shù)據(jù)拼成結(jié)果集

查詢(xún)的列很少 走內(nèi)存臨時(shí)表

select word from t where time <2020-05-06 order by word;


內(nèi)存臨時(shí)表的儲(chǔ)存引擎不是innodb所以需要“位置"這種數(shù)據(jù)

如果有分頁(yè),會(huì)將符合條件的數(shù)據(jù)都查出然后計(jì)算需要返回多少行到多少行

如果where條件和要排序的列屬于同一個(gè)索引那就不需要排序,因?yàn)槟玫降臄?shù)據(jù)就是有序的

查詢(xún)的列很多就根據(jù)索引走磁盤(pán)

select city,name,age,id_card, address,time from t where city = "杭州" order by name;


大查詢(xún)會(huì)不會(huì)把MySQL內(nèi)存打爆?

不會(huì)

mysql采用的策略是“邊讀邊發(fā)”,mysql引入netBuffer的概念,一塊內(nèi)存空間,每次將讀取到的數(shù)據(jù)放入netBuffer中netBuffer滿(mǎn)了之后調(diào)用網(wǎng)絡(luò)接口將數(shù)據(jù)發(fā)送出去然后將netBuffer清空進(jìn)入下一輪的讀取,如果本地網(wǎng)絡(luò)棧寫(xiě)滿(mǎn)了就等待直到網(wǎng)絡(luò)棧重新可寫(xiě)再次發(fā)送

三、數(shù)據(jù)庫(kù)的事務(wù)

讀未提交 讀已提交 可重復(fù)讀 可重復(fù)讀是站在當(dāng)前事務(wù)的角度來(lái)說(shuō)的

事務(wù)的屬性:acid 原子性? 一致性 隔離性 持久性

事務(wù)解決的問(wèn)題:臟讀 不可重復(fù)讀 幻讀

四個(gè)隔離級(jí)別:

讀未提交:可以讀取到未交的數(shù)據(jù)

讀已提交:可以讀取到提交后的數(shù)據(jù)

可重復(fù)讀:在A事務(wù)中對(duì)某條數(shù)據(jù)讀取了兩次,這兩次中間B事務(wù)對(duì)該條數(shù)據(jù)做了修改,但是A事務(wù)兩次讀取到的還是一樣的數(shù)據(jù)(MySQL默認(rèn)的隔離級(jí)別)

串行化:讀加讀鎖,寫(xiě)加寫(xiě)鎖


我們來(lái)看看在不同的隔離級(jí)別下,事務(wù) A 會(huì)有哪些不同的返回結(jié)果,也就是圖里面 V1、V2、V3 的返回值分別是什么。

若隔離級(jí)別是“讀未提交”, 則 V1 的值就是 2。這時(shí)候事務(wù) B 雖然還沒(méi)有提交,但是結(jié)果已經(jīng)被 A 看到了。因此,V2、V3 也都是 2。

若隔離級(jí)別是“讀提交”,則 V1 是 1,V2 的值是 2。事務(wù) B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。

若隔離級(jí)別是“可重復(fù)讀”,則 V1、V2 是 1,V3 是 2。之所以 V2 還是 1,遵循的就是這個(gè)要求:事務(wù)在執(zhí)行期間看到的數(shù)據(jù)前后必須是一致的。

若隔離級(jí)別是“串行化”,則在事務(wù) B 執(zhí)行“將 1 改成 2”的時(shí)候,會(huì)被鎖住。直到事務(wù) A 提交后,事務(wù) B 才可以繼續(xù)執(zhí)行。所以從 A 的角度看, V1、V2 值是 1,V3 的值是 2。

隔離級(jí)別的實(shí)現(xiàn)方式:視圖(read-view)和鎖、回滾日志

數(shù)據(jù)庫(kù)里會(huì)創(chuàng)建一個(gè)視圖,訪問(wèn)的時(shí)候以視圖的邏輯為準(zhǔn)

讀未提交:直接返回記錄上的最新值

讀已提交:在SQL語(yǔ)句執(zhí)行的時(shí)候創(chuàng)建

可重復(fù)讀:在事務(wù)開(kāi)始的時(shí)候創(chuàng)建

串行化:直接通過(guò)加鎖的方式來(lái)避免并行訪問(wèn),當(dāng)讀寫(xiě)鎖沖突的時(shí)候,后一個(gè)事務(wù)等前一個(gè)事務(wù)執(zhí)行完在執(zhí)行

回滾日志:

MySQL的每次變更都會(huì)記錄一條對(duì)應(yīng)的回滾操作,記錄上的最新值可以通過(guò)回滾操作得到前一個(gè) 狀態(tài)的值

當(dāng)一個(gè)事務(wù)回滾時(shí),可一個(gè)通過(guò)回滾操作恢復(fù)數(shù)據(jù)

當(dāng)沒(méi)有視圖需要回滾日志時(shí),回滾日志就會(huì)刪除

事務(wù)的詳解

請(qǐng)仔細(xì)看一遍:第八講事務(wù)到底是隔離的還是不隔離的

MVCC一行數(shù)據(jù)只有一個(gè)記錄不是有多個(gè)紀(jì)錄,但是會(huì)記錄當(dāng)前最新的版本號(hào),如果想看到之前的版本需要根據(jù)undo log 來(lái)計(jì)算

行的版本號(hào)和事務(wù)的id是一致的

可重復(fù)讀的事務(wù)里有修改語(yǔ)句修改語(yǔ)句是當(dāng)前讀

MVCC適用于“可重復(fù)讀”,思考 讀未提交,讀已提交,insert語(yǔ)句 是怎么處理可見(jiàn)性的

長(zhǎng)事務(wù)對(duì)MySQL的影響?

一個(gè)事務(wù)的執(zhí)行時(shí)間過(guò)長(zhǎng),在整個(gè)事務(wù)中回滾段一直未被清理,大量的回滾日志占用很多磁盤(pán)空間

同時(shí)還占用鎖資源,極大加重了整個(gè)庫(kù)的壓力

幻讀的概念

讀到了“新插入的數(shù)據(jù)行”

同一個(gè)事務(wù)中兩次查詢(xún)第二次查到了新插入的數(shù)據(jù)行(普通的查詢(xún)是“快照讀”,“當(dāng)前讀”的情況下才會(huì)出現(xiàn)“幻讀”)


最后一行出現(xiàn)了幻讀

四、索引

InnoDB 的數(shù)據(jù)是保存在主鍵索引上的

平衡二叉樹(shù) B+樹(shù)? https://www.toutiao.com/a6624750730471277059/

索引覆蓋 :想要查詢(xún)的結(jié)果集就是索引不用回表

索引最左原則:ABC索引 適合 A? AB ABC 也是適合 like "xx%"

索引下推 :聯(lián)合索引 "name,age"? select * from custom where name like "張%" and age = 10;

為什么主鍵自增要好?

1.自增起始數(shù)據(jù)小占用空間小

2.索引采用的是B+樹(shù),數(shù)據(jù)自增可以減少“分裂”和“合并”

? 分裂:當(dāng)出現(xiàn)中間數(shù)據(jù)時(shí),B+樹(shù)會(huì)觸發(fā)分裂,MySQL也會(huì)申請(qǐng)新的數(shù)據(jù)將原來(lái)數(shù)據(jù)頁(yè)中分出的數(shù)據(jù)拷貝過(guò)去,這個(gè)過(guò)程比較消耗資源同時(shí)原來(lái)的數(shù)據(jù)頁(yè)未被使用完浪費(fèi)了空間

? 合并:當(dāng)相鄰的兩個(gè)數(shù)據(jù)頁(yè)刪除了數(shù)據(jù),可能觸發(fā)合并是分裂的逆過(guò)程同樣消耗性能

為什么B+樹(shù)適合建索引?

1.B+樹(shù)非葉子節(jié)點(diǎn)沒(méi)有關(guān)鍵字指針只存有關(guān)鍵字,非葉子節(jié)點(diǎn)可以存放更多的數(shù)據(jù),所有樹(shù)的高度比較低,查詢(xún)的速度更快

2.非葉子節(jié)點(diǎn)存放的都是索引,數(shù)據(jù)都存放在葉子節(jié)點(diǎn),方便掃庫(kù)也適合區(qū)間查詢(xún),像B樹(shù)非葉子節(jié)點(diǎn)也存放有數(shù)據(jù),找到具體的數(shù)據(jù)需要進(jìn)行中序遍歷才可以

普通索引和唯一索引在查詢(xún)性能上的差異大不大?

不大

1.唯一索引在索引樹(shù)上讀到符合條件的數(shù)據(jù)就不再讀取

2.普通索引是讀取到符合條件的數(shù)據(jù)之后會(huì)接著讀取下一條,直到讀到不符合條件的數(shù)據(jù)為止

3.innodb數(shù)據(jù)的讀取是以頁(yè)為單位來(lái)讀取的,一次會(huì)將一頁(yè)數(shù)據(jù)從磁盤(pán)中全部讀出一頁(yè)默認(rèn)大小16kb

所以差別不大

字符串索引的三個(gè)小技巧:

1.采用前綴索引,將字符串的前幾個(gè)值作為索引,可以減少空間占用

2.像身份證號(hào)這種數(shù)據(jù) 前面的數(shù)字都相同后邊幾位不同,存儲(chǔ)的時(shí)候可以倒著存儲(chǔ)然后用前綴索引

3.將長(zhǎng)字符串取hash值通過(guò)hash值建立索引

where條件中有函數(shù)不走索引:

select * from tradelog where tradeid=110717;(tradeild列是varchar類(lèi)型有索引,但是這條語(yǔ)句卻沒(méi)走索引)

select * from tradelog where? CAST(tradid AS signed int) = 110717;(因?yàn)閠ardeild是varchar類(lèi)型所以?xún)?yōu)化器需要將110717轉(zhuǎn)換為字符串類(lèi)型,然后再執(zhí)行,轉(zhuǎn)換使用了函數(shù)所以不適用索引)

兩個(gè)表的字符集不同,一個(gè)是 utf8,一個(gè)是 utf8mb4,所以做表關(guān)聯(lián)查詢(xún)的時(shí)候用不上關(guān)聯(lián)字段的索引

兩個(gè)表字符集不同在優(yōu)化器會(huì)使用函數(shù)修改sql將關(guān)聯(lián)的那一列的數(shù)據(jù)的字符集修改為一樣的,因?yàn)槭褂昧撕瘮?shù)所以不走索引

五、數(shù)據(jù)庫(kù)鎖

全局鎖:FTWRL將整個(gè)庫(kù)設(shè)置成只讀狀態(tài),增刪改,建表,修改表的操作都不能執(zhí)行,影響范圍很大,同時(shí)也影響主從同步;如果是innodb可以使用事務(wù)(可重復(fù)讀)也可以實(shí)現(xiàn)多次讀是一樣的數(shù)據(jù)

表鎖:

顯示加鎖,lock tables T read/write

非顯示加鎖,MDL 當(dāng)訪問(wèn)表時(shí)(增刪改查)數(shù)據(jù)庫(kù)默認(rèn)加上表鎖讀鎖,當(dāng)對(duì)數(shù)據(jù)表有結(jié)構(gòu)修改時(shí)默認(rèn)加上表鎖寫(xiě)鎖,讀鎖之間不互斥可以有多個(gè),讀鎖和讀鎖,寫(xiě)鎖和讀鎖之間互斥

修改表結(jié)構(gòu)導(dǎo)致鎖表的問(wèn)題 就是因?yàn)樽x寫(xiě)鎖互斥導(dǎo)致的,寫(xiě)鎖沒(méi)有獲取到會(huì)一直阻塞后邊的請(qǐng)求,當(dāng)寫(xiě)鎖獲取到后 執(zhí)行了修改表結(jié)構(gòu)的語(yǔ)句 后邊的業(yè)務(wù)語(yǔ)句才能執(zhí)行

如何安全的給表加字段?

1.執(zhí)行alter table語(yǔ)句之前確定沒(méi)有長(zhǎng)事務(wù)

2.執(zhí)行語(yǔ)句中加入等待時(shí)間,在等待時(shí)間內(nèi)沒(méi)有獲取到寫(xiě)鎖,先不執(zhí)行 讓業(yè)務(wù)語(yǔ)句執(zhí)行;

行鎖:給掃描到的所有行加鎖,行鎖是存儲(chǔ)引擎層面的概念不同的存儲(chǔ)引擎有不同的行鎖實(shí)現(xiàn)方式,MyISAM沒(méi)有行鎖

事務(wù)中的行鎖,在sql執(zhí)行時(shí)創(chuàng)建在事務(wù)提交后釋放

行鎖死鎖的例子:


如何解決死鎖問(wèn)題?

1.設(shè)置鎖的等待時(shí)間 innodb_lock_wait_timeout 超過(guò)時(shí)間 第一個(gè)被鎖住的線程要過(guò) 50s默認(rèn) 才會(huì)超時(shí)退出,然后其他線程才有可能繼續(xù)執(zhí)行

2.設(shè)置死鎖檢測(cè) 參數(shù) innodb_deadlock_detect 設(shè)置為 on 發(fā)現(xiàn)死鎖后,主動(dòng)回滾死鎖鏈條中的某一個(gè)事務(wù),讓其他事務(wù)得以繼續(xù)執(zhí)行

3.也可以從數(shù)據(jù)表的設(shè)計(jì)上避免,像進(jìn)行賬號(hào)金額的操作,將一個(gè)賬號(hào)改為多個(gè)賬號(hào),每次需要處理余額可以隨機(jī)選取一個(gè)賬號(hào)來(lái)處理,最終所有的賬號(hào)余額的和為總賬號(hào)的余額,這樣減少了行死鎖的概率;

當(dāng)前讀的概念:

修改語(yǔ)句是當(dāng)前讀,是先查詢(xún)?cè)傩薷?/p>

即使一個(gè)修改語(yǔ)句在可重復(fù)讀的事務(wù)里,也會(huì)去讀取最新的已提交的數(shù)據(jù)

修改語(yǔ)句同時(shí)會(huì)給涉及的行加寫(xiě)鎖,(沒(méi)有事務(wù)的情況下)語(yǔ)句執(zhí)行完鎖釋放

select語(yǔ)句后加for update也是同樣的操作(當(dāng)前讀 加寫(xiě)鎖)

mysql是如何運(yùn)用行鎖、間歇鎖解決幻讀的?

update t set c = 5 where d = 2;

update語(yǔ)句會(huì)添加寫(xiě)鎖

假如d列沒(méi)有索引,就會(huì)觸發(fā)全局掃描,mysql會(huì)給所有掃描到的所有行加鎖

insert into t (id , c , d),(1,4,2);

新插入一條d=5的數(shù)據(jù),新插入的數(shù)據(jù)沒(méi)有被掃描到不會(huì)加鎖(容易引發(fā)幻讀)

間歇鎖:將涉及到的行的區(qū)間給加鎖(包括兩端)

下圖中間歇鎖 鎖定的區(qū)間有(-負(fù)無(wú)窮,0],(0,5],(5,10],(10,15],(15,20],(20,25],(25,正無(wú)窮]


有了間歇鎖就能解決幻讀的問(wèn)題


間歇鎖的兩原則,兩優(yōu)化,一個(gè)bug

原則 1:加鎖的基本單位是 next-key lock。希望你還記得,next-key lock 是前開(kāi)后閉區(qū)間。

原則 2:查找過(guò)程中訪問(wèn)到的對(duì)象才會(huì)加鎖。

優(yōu)化 1:索引上的等值查詢(xún),給唯一索引加鎖的時(shí)候,next-key lock 退化為行鎖。

優(yōu)化 2:索引上的等值查詢(xún),向右遍歷時(shí)且最后一個(gè)值不滿(mǎn)足等值條件的時(shí)候,next-key lock 退化為間隙鎖。

一個(gè) bug:唯一索引上的范圍查詢(xún)會(huì)訪問(wèn)到不滿(mǎn)足條件的第一個(gè)值為止。

間歇鎖鎖住行的范圍更大,更容易導(dǎo)致死鎖

六、表空間

1.innodb中表數(shù)據(jù)的存儲(chǔ)方式

表結(jié)構(gòu):MySQL8.0前是存放在單獨(dú)的文件里,8.0之后是存儲(chǔ)在系統(tǒng)數(shù)據(jù)表里

表數(shù)據(jù):可以通過(guò)配置選擇 一種是存儲(chǔ)在共享表空間,一種是一張表一個(gè)文件的形式存儲(chǔ)

為什么刪除了表數(shù)據(jù)表空間大小沒(méi)變?

mysql中數(shù)據(jù)是以數(shù)據(jù)頁(yè)的形式存儲(chǔ)

當(dāng)刪除一行數(shù)據(jù)時(shí),會(huì)將這個(gè)行標(biāo)記為可復(fù)用

當(dāng)刪除也一頁(yè)數(shù)據(jù)時(shí)會(huì)將這個(gè)頁(yè)標(biāo)記為可復(fù)用

所以刪除數(shù)據(jù)后占用的表空間不會(huì)變

如果真正的想收縮表空間可以通過(guò)重建表的方式實(shí)現(xiàn),重建的過(guò)程中會(huì)重建索引重新分配內(nèi)存

? ? ? 命令:alter table A engine=InnoDB? 這是一個(gè)DDL的過(guò)程全程不能用寫(xiě)和修改的操作

? ? ? mysql5.7之后有online DDL的操作 重建表的過(guò)程中允許添加和修改,它會(huì)把修改和添加的操作記錄到日志文件中,重建完成之后將日志里的邏輯執(zhí)行一遍

MDL:表級(jí)鎖

DML:指的是增刪改查的操作

DDL:指的是修改表結(jié)構(gòu)的操作

七、性能分析

一條簡(jiǎn)單的查詢(xún)語(yǔ)句查詢(xún)速度很慢

mysql> CREATE TABLE `t` (

? `id` int(11) NOT NULL,

? `c` int(11) DEFAULT NULL,

? PRIMARY KEY (`id`)

) ENGINE=InnoDB;

select * from t where id=1;

1.等DML鎖

show processlist 可以查看當(dāng)前sql語(yǔ)句的執(zhí)行情況

是表被鎖住了,找到鎖表的線程直接kill掉

2.等f(wàn)lush

flush tables t;

本身flush執(zhí)行速度很快,可是flush如果被其他事物阻塞了,flush就會(huì)阻塞新的查詢(xún)語(yǔ)句

其他事務(wù)中對(duì)t表有操作,flush會(huì)等待其他事務(wù)執(zhí)行完再對(duì)t表進(jìn)行flush

這個(gè)時(shí)候還是通過(guò) show processlist命令查看當(dāng)前sql執(zhí)行情況

3.等待行鎖

select * from t where id = 2; select語(yǔ)句默認(rèn)不加讀鎖

select * from t where id = 2lock in share mode;顯式加讀鎖

當(dāng)前的行在其他事務(wù)中有寫(xiě)鎖,這條語(yǔ)句就會(huì)等待寫(xiě)鎖釋放才能讀取

4.數(shù)據(jù)量很大也沒(méi)有索引

5.一致性讀,讀取的是老版本的數(shù)據(jù),其他事務(wù)做了大量修改,需要執(zhí)行大量的回滾日志才能看到老數(shù)據(jù);

短連接風(fēng)暴

一般的短連接模式就是,連接數(shù)據(jù)庫(kù)之后執(zhí)行很少的sql后就斷開(kāi),再次需要的時(shí)候再次連接

風(fēng)險(xiǎn):一旦數(shù)據(jù)庫(kù)處理得慢一些,連接數(shù)就會(huì)暴漲超過(guò)了max_connections其他業(yè)務(wù)就不能連接數(shù)據(jù)庫(kù)

弊端:MySQL 建立連接的過(guò)程,成本是很高的。除了正常的網(wǎng)絡(luò)連接三次握手外,還需要做登錄權(quán)限判斷和獲得這個(gè)連接的數(shù)據(jù)讀寫(xiě)權(quán)限。

處理:show prossicelist 查看空閑的連接直接kill掉

? ? ? ? ? 為設(shè)置wait timeout

慢查詢(xún)性能問(wèn)題

在 MySQL 中,會(huì)引發(fā)性能問(wèn)題的慢查詢(xún),大體有以下三種可能:

索引沒(méi)有設(shè)計(jì)好;創(chuàng)建索引,修改表結(jié)構(gòu)會(huì)觸犯表鎖 但是現(xiàn)在執(zhí)行 online DDL,同時(shí)可以采用在備份庫(kù)中執(zhí)行alter table 然后切換主備? 再在另外一個(gè)庫(kù)中執(zhí)行alter table

SQL 語(yǔ)句沒(méi)寫(xiě)好; sql優(yōu)化、行鎖、表鎖、flush

MySQL 選錯(cuò)了索引。 可以強(qiáng)制使用某個(gè)索引

如何判斷一個(gè)數(shù)據(jù)庫(kù)還能否正常使用?

select 1 判斷進(jìn)程是否正常

select * from t 判斷查詢(xún)是否正常

查看updateTime(類(lèi)型timestamp)字段 判斷修改是否正常

可以通過(guò)命令查看磁盤(pán)io時(shí)間,如果頻繁出現(xiàn)超時(shí)就是有性能問(wèn)題

庫(kù)里的表越多連接是否就越慢?

連接的過(guò)程中服務(wù)端就三個(gè)功能:TCP握手,用戶(hù)校驗(yàn),獲取權(quán)限

這三個(gè)功能點(diǎn)和表的數(shù)據(jù)量無(wú)關(guān)所以不會(huì)影響速度

客戶(hù)端會(huì)有補(bǔ)全表名,補(bǔ)全庫(kù)名的功能,這個(gè)功能需要需要構(gòu)建本地哈希表 這個(gè)和表的數(shù)量有關(guān)

sql優(yōu)化

1.查詢(xún)一個(gè)大表沒(méi)有索引

建立索引

2.關(guān)聯(lián)查詢(xún)的是一個(gè)冷數(shù)據(jù)大表沒(méi)有索引

t2表數(shù)據(jù)行超過(guò)200萬(wàn)行,b字段沒(méi)有索引

select * from t1 join t2 on (t1.b=t2.b) where t2.b>=1 and t2.b<=2000;

改為:

create temporary table temp_t(id int primary key, a int, b int, index(b))engine=innodb;

insert into temp_t select * from t2 where b>=1 and b<=2000;

select * from t1 join temp_t on (t1.b=temp_t.b);

大表索引占用空間較大,又是冷數(shù)據(jù)所以沒(méi)必要建立索引

可以建立臨時(shí)表,給臨時(shí)表建立索引

讓sql可以走BKA算法

3.保潔商家打查詢(xún)案例BAK

查詢(xún)封裝商家基本信息和商家其他信息

crm_custom? 商家基本信息表

custom_id

crm_custom_sign? 商家簽約表

custom_id

service_id 201

crm_custom_level? 商家等級(jí)表

custom_id

crm_custom_category_status 商家狀態(tài)表

custom_id

其他表

原始sql:

select * from crm_custom a left join crm_custom_sign b on a.custom_id = b.custom_id

left join crm_custom_level c on a.custom_id = c.custom_id left join crm_custom_category_status d on a.custom_id = d.custom_id

where b.service_id = 201 limit0,30;

一百多萬(wàn)數(shù)據(jù)得執(zhí)行八個(gè)小時(shí)

優(yōu)化后:

customIds =

select a.custom_id from crm_custom a left join crm_custom_sign b where a.custom_id = b.custom_id where b.service_id = 201 order by a.custom_id desc limit 0,30 (crm_custom_sign 的custom_id有索引)? 這一步走了索引覆蓋

customBeans=

select * from crm_custom from custom_id in (customIds);

這一步走了主鍵索引而且參數(shù)是有序的 BKA

for循環(huán)customBeans 根據(jù)customId去各個(gè)表中獲取想要列?

這一步走了普通索引

優(yōu)化后這個(gè)流程十分鐘

4.分庫(kù)分表后使用臨時(shí)表優(yōu)化查詢(xún)

t 表通f列做key分發(fā)到24個(gè)庫(kù)上

select * from t where a > M order by b limit 0,100;

由于a不是key所以會(huì)去24個(gè)庫(kù)分別執(zhí)行這個(gè)sql

正常思路:在24個(gè)庫(kù)執(zhí)行完sql拿到 24*100 = 2400條數(shù)據(jù) 然后通過(guò)程序按照b字段進(jìn)行排序,然后取出前100條

使用臨時(shí)表的方式:

在24個(gè)庫(kù)中執(zhí)行sql取出2400條數(shù)據(jù)

選擇一個(gè)庫(kù)創(chuàng)建臨時(shí)表 create temporaty t_temp

將查詢(xún)到的2400放入臨時(shí)表

在臨時(shí)表上執(zhí)行sql select * from t_temp where a>M order by b limit 0,100;

這種場(chǎng)景使用臨時(shí)表的好處:

分庫(kù)后如果數(shù)據(jù)量較大又有 join order by 這樣的復(fù)雜查詢(xún) 如果使用程序處理對(duì)內(nèi)存 CPU要求很高

如果使用臨時(shí)表能夠直接使用mysql來(lái)處理 mysql本身有很好的優(yōu)化方案

5.盡量使用普通索引不使用唯一索引

6.limit分頁(yè)參數(shù)過(guò)大為什么很慢?

原因:select * from t limit 300000,10; 需要掃描 300000+10行

? ? ? ? ? select * from t limit 0,10; 需要掃描10行

解決方法:

記錄上次查詢(xún)的最大id:a

select * from t where id > a order by id asc limit 0,10;

八、主備一致和讀寫(xiě)分離

A庫(kù):主庫(kù)? B庫(kù):備庫(kù)(m-s結(jié)構(gòu))

核心是通過(guò)binlog來(lái)實(shí)現(xiàn)主備一致

1.在備庫(kù)B上通過(guò)change master命令,設(shè)置主庫(kù)A的IP、端口、用戶(hù)名、密碼以及從哪個(gè)位置開(kāi)始請(qǐng)求binlog日志,這個(gè)位置包含文件名和日志偏移量

2.在備庫(kù)上執(zhí)行startslave命令這時(shí)候會(huì)啟動(dòng)兩個(gè)線程,io_thread 和 sql_thread,其中io_thread負(fù)責(zé)與主庫(kù)建立連接

3.主庫(kù)校驗(yàn)完用戶(hù)名和密碼之后通過(guò)備份庫(kù)B傳過(guò)來(lái)的位置,從本讀取binlog發(fā)送給B

4.備份庫(kù)B拿到binlog后開(kāi)始寫(xiě)本地文件,叫"中轉(zhuǎn)日志"relay log

5.sql_threda讀取中轉(zhuǎn)日志,解析日志里的命令然后執(zhí)行

A和B庫(kù)互為主備關(guān)系(雙M結(jié)構(gòu))

MySQL 在 binlog 中記錄了這個(gè)命令第一次執(zhí)行時(shí)所在實(shí)例的 server id

從節(jié)點(diǎn) A 更新的事務(wù),binlog 里面記的都是 A 的 server id;

傳到節(jié)點(diǎn) B 執(zhí)行一次以后,節(jié)點(diǎn) B 生成的 binlog 的 server id 也是 A 的 server id;

再傳回給節(jié)點(diǎn) A,A 判斷到這個(gè) server id 與自己的相同,就不會(huì)再處理這個(gè)日志。所以,死循環(huán)在這里就斷掉了。

一般情況下將從庫(kù)設(shè)置為only read,但是主備同步使用的是super 用戶(hù),只讀對(duì)super用戶(hù)無(wú)效

binlog記錄日志的三種形式

1.statement 記錄執(zhí)行的sql

2.row 根據(jù)主鍵記錄數(shù)據(jù)的變化

3.mixed是statement和row的綜合(根據(jù)情況選擇使用statement或者row)

三種方式的比較:

安全:

delete from t where a = 1 and b > "2020.02.02" limit 1;(a有索引,b也有索引)

如果采用statement 同樣一條sql在不一樣的庫(kù)中可能刪除了不一樣的行,因?yàn)樽吡瞬灰粯拥乃饕?/p>

如果采用row就不會(huì)出現(xiàn)這樣的問(wèn)題

如果采用mixed數(shù)據(jù)庫(kù)會(huì)根據(jù)安全情況主動(dòng)選擇row

性能:

statement:只需要記錄相關(guān)sql就好了

row:需要記錄所有涉及到的行 如果一條sql涉及十萬(wàn)行就得記錄十萬(wàn)行

主備延遲的原因

備庫(kù)的同步在一段時(shí)間內(nèi)完全被堵住

1.主庫(kù)起了一個(gè)大事務(wù)(包括大表的DDL、or一個(gè)事務(wù)操作很多行)

2.備庫(kù)起了一個(gè)長(zhǎng)事務(wù),長(zhǎng)時(shí)間占用線程

未完全堵住延遲時(shí)間較長(zhǎng)

1.主庫(kù)是多線程,從庫(kù)是單線程,從庫(kù)跟不上更新的速度

2.還有就是一些主從復(fù)制的策略也會(huì)影響

在主從的基礎(chǔ)上可以實(shí)現(xiàn)讀寫(xiě)分離

如何解決讀寫(xiě)分離中從庫(kù)延遲的問(wèn)題?(數(shù)據(jù)同步的延遲)

1.強(qiáng)制走主庫(kù)

對(duì)應(yīng)一致要求特別高的數(shù)據(jù)讀寫(xiě)都走主庫(kù)

2.sleep

讀取從庫(kù)的數(shù)據(jù)前先sleep一下保證數(shù)據(jù)已經(jīng)同步完畢

3.判斷主備無(wú)延遲方案

第一種方法:每次從庫(kù)執(zhí)行查詢(xún)請(qǐng)求前,先判斷 seconds_behind_master 是否已經(jīng)等于 0。如果還不等于 0 ,那就必須等到這個(gè)參數(shù)變?yōu)?0 才能執(zhí)行查詢(xún)請(qǐng)求(不完全準(zhǔn) 單位是秒)

第二種方法,對(duì)比位點(diǎn)確保主備無(wú)延遲:保證binlog同步完也都執(zhí)行了

Master_Log_File 和 Read_Master_Log_Pos,表示的是讀到的主庫(kù)的最新位點(diǎn);

Relay_Master_Log_File 和 Exec_Master_Log_Pos,表示的是備庫(kù)執(zhí)行的最新位點(diǎn)。

如果 Master_Log_File 和 Relay_Master_Log_File、Read_Master_Log_Pos 和 Exec_Master_Log_Pos 這兩組值完全相同,就表示接收到的日志已經(jīng)同步完成。

第三種方法,對(duì)比 GTID 集合確保主備無(wú)延遲:確認(rèn)binlog都同步完也都執(zhí)行了

Auto_Position=1 ,表示這對(duì)主備關(guān)系使用了 GTID 協(xié)議。

Retrieved_Gtid_Set,是備庫(kù)收到的所有日志的 GTID 集合;

Executed_Gtid_Set,是備庫(kù)所有已經(jīng)執(zhí)行完成的 GTID 集合。

如果這兩個(gè)集合相同,也表示備庫(kù)接收到的日志都已經(jīng)同步完成。

(第二種、第三種方法比一種準(zhǔn) 但是還不夠 因?yàn)橹鲙?kù)執(zhí)行完的日志可能還沒(méi)完全提交過(guò)來(lái))

4.配合semi-sync

簽收機(jī)制

事務(wù)提交的時(shí)候,主庫(kù)把 binlog 發(fā)給從庫(kù);

從庫(kù)收到 binlog 以后,發(fā)回給主庫(kù)一個(gè) ack,表示收到了;

主庫(kù)收到這個(gè) ack 以后,才能給客戶(hù)端返回“事務(wù)完成”的確認(rèn)。

判斷主備無(wú)延遲方案? 配合semi-sync? 配合使用可以保證主從的一致

5.GTID方案

數(shù)據(jù)庫(kù)開(kāi)啟GTID模式

等待最新的事務(wù)同步完再執(zhí)行查詢(xún)

九、join語(yǔ)句的執(zhí)行過(guò)程

select * from t1 straight_join t2 on (t1.a=t2.a);? t2 的a字段不是主鍵但是有索引

sql執(zhí)行過(guò)程

1.從t1表中讀取一行數(shù)據(jù)R

2.從數(shù)據(jù)行R中取出字段a到t2表中去找

3.找到符合條件的行,和R組成一行作為結(jié)果集的一部分

4.重復(fù)執(zhí)行1到3知道t1表的結(jié)尾循環(huán)結(jié)束

分析:

t1表是驅(qū)動(dòng)表? t2是被驅(qū)動(dòng)表

1.對(duì)t1表做了全表掃描 全表行數(shù)h1

2.而對(duì)每一行R,根據(jù)R中的a去t2表搜索走了索引,假如a在t2表中不重復(fù),那么t2表業(yè)掃描了h1行

3.所以整個(gè)流程掃描了2h1行數(shù)據(jù)

上邊這個(gè)sql中假如t1表有N行? t2表有M行

時(shí)間復(fù)雜度為? o(n) = N + N*2*log2M;

N對(duì)時(shí)間復(fù)雜度的影響是主要的 所有需要小表驅(qū)動(dòng)大表

select * from t1 straight_join t2 on (t1.a=t2.b); t2的b字段不是主鍵也沒(méi)有索引

t1表N行,t2表M行

sql執(zhí)行過(guò)程

最笨的思路:全局掃描t1表,逐行讀取R,取出R中的a值,全局掃描t2表逐條取出R2將R2中的b值和a值比較符合則拼裝結(jié)果集,這樣t2表需要全局掃描N次? 總共掃描的行為 N+N*M;

mysql真正的思路:

1.全局掃描t1表將t1表全部放入線程級(jí)的內(nèi)存join_buffer

2.全局掃描t2表,逐條和join_buffer中的數(shù)據(jù)做對(duì)比得出符合條件的數(shù)據(jù)

兩張表都只掃描一次 總共掃描的行數(shù)是:N+M

如果t1表太大join_buffer一次裝不下怎么辦?

答:那就分多次裝

每多裝一次t2表就需要多做一次全局掃描,用t2表中的所有數(shù)據(jù)和每個(gè)join_buffer做對(duì)比

總共掃描的行:N+k*M

可以表示為:N + λ*N*M

所以N對(duì)行數(shù)的影響還是最大還是應(yīng)該:小表驅(qū)動(dòng)大表

如果不強(qiáng)制性指定驅(qū)動(dòng)關(guān)系,優(yōu)化器會(huì)自動(dòng)設(shè)置小表驅(qū)動(dòng)大表

進(jìn)一步的優(yōu)化BKA?

十、臨時(shí)表的解釋

內(nèi)存表的概念:

1.數(shù)據(jù)都保存在內(nèi)存中,系統(tǒng)重啟時(shí)數(shù)據(jù)會(huì)被清空但是表結(jié)構(gòu)還在

2.內(nèi)存表只能使用memory存儲(chǔ)引擎

其他屬性和正常的數(shù)據(jù)表沒(méi)有區(qū)別

臨時(shí)表的概念:

1.臨時(shí)表是相對(duì)于事務(wù)而言的,一個(gè)臨時(shí)表只對(duì)一個(gè)事務(wù)可見(jiàn),兩個(gè)事務(wù)中有臨時(shí)表的名稱(chēng)相同是互不影響的, session 結(jié)束的時(shí)候,會(huì)自動(dòng)刪除臨時(shí)表

2.臨時(shí)表數(shù)據(jù)是寫(xiě)在磁盤(pán)上的,可以使用各種存儲(chǔ)引擎

3.臨時(shí)表可以與普通表同名。

4.session A 內(nèi)有同名的臨時(shí)表和普通表的時(shí)候,show create 語(yǔ)句,以及增刪改查語(yǔ)句訪問(wèn)的是臨時(shí)表。

5.show tables 命令不顯示臨時(shí)表

十一、分表

1.分表的key選取

1.按時(shí)間字段分表,方便擴(kuò)張

2.按全局主鍵分表,方便查詢(xún)

3.結(jié)合可以預(yù)估范圍的字段具體分析,根據(jù)業(yè)務(wù)有的也可按地域?qū)傩詣澐?/p>

分區(qū)分表:

CREATE TABLE `t` (

`ftime` datetime NOT NULL,

`c` int(11) DEFAULT NULL,

KEY (`ftime`)

) ENGINE=InnoDB DEFAULT CHARSET=latin1

PARTITION BY RANGE (YEAR(ftime))

(PARTITION p_2017 VALUES LESS THAN (2017) ENGINE = InnoDB,

PARTITION p_2018 VALUES LESS THAN (2018) ENGINE = InnoDB,

PARTITION p_2019 VALUES LESS THAN (2019) ENGINE = InnoDB,

PARTITION p_others VALUES LESS THAN MAXVALUE ENGINE = InnoDB);

insert into t values('2017-4-1',1),('2018-4-1',1);

我在表 t 中初始化插入了兩行記錄,按照定義的分區(qū)規(guī)則,這兩行記錄分別落在 p_2018 和 p_2019 這兩個(gè)分區(qū)上。

可以看到,這個(gè)表包含了一個(gè).frm 文件和 4 個(gè).ibd 文件,每個(gè)分區(qū)對(duì)應(yīng)一個(gè).ibd 文件。也就是說(shuō):

對(duì)于引擎層來(lái)說(shuō),這是 4 個(gè)表;

對(duì)于 Server 層來(lái)說(shuō),這是 1 個(gè)表

分表后鎖性能的分析:

間隙鎖會(huì)被阻斷

表鎖也只能作用于掃描到的分區(qū)(有多個(gè)分區(qū)相當(dāng)于有多個(gè)表)

分區(qū)的應(yīng)用場(chǎng)景

1.較大的歷史記錄表,可以按時(shí)間分區(qū)

2.分區(qū)之后業(yè)務(wù)代碼更簡(jiǎn)潔

分庫(kù)分表

訂單系統(tǒng)分庫(kù)分表拆分案例

整體大的步驟:

1.原來(lái)的庫(kù)保持不同,業(yè)務(wù)上雙寫(xiě)

2.引入es同步數(shù)據(jù)庫(kù)表數(shù)據(jù)

3.非key字段為條件的查詢(xún)?nèi)绾翁幚?/p>

? 1)實(shí)時(shí)性要求不高的走es查詢(xún)

? 2)數(shù)據(jù)一致性要求高的走原來(lái)的全量的庫(kù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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