什么是 MySQL 全局鎖、表鎖、行鎖?

01 前言

猛男真的讓人不省心。繼上次猛男誤刪數(shù)據(jù)之后,這次這貨直接給我把整個(gè)表鎖住了。頁(yè)面無(wú)響應(yīng),用戶(hù)瘋狂投訴,我特么臉都綠了。。。

事情是這樣的,線上有個(gè)數(shù)據(jù)庫(kù)幾十萬(wàn)的數(shù)據(jù),由于一開(kāi)始沒(méi)做好規(guī)劃并沒(méi)有給熱點(diǎn)字段加索引。我就讓猛男有空加個(gè)索引,沒(méi)想到這貨在用戶(hù)使用高峰期加。。。

知道原因,我還是比較淡定的。畢竟最近都在研究 MySQL,對(duì)于 MySQL 鎖的問(wèn)題解決起來(lái)還是得心應(yīng)手。猛男見(jiàn)我三兩下就解決了問(wèn)題,客戶(hù)也給出了臥槽,牛逼的肯定,忙問(wèn)我怎么解決的,我點(diǎn)燃手中 82 年的華子深深吸了一口,花了幾個(gè)小時(shí)寫(xiě)了這篇文章給它。

全文 5665 字,從下午四點(diǎn)寫(xiě)到晚上九點(diǎn),先上張思維導(dǎo)圖鎮(zhèn)樓:

02 全局鎖

全局鎖是對(duì)整個(gè)數(shù)據(jù)庫(kù)實(shí)例加鎖,讓其處于只讀狀態(tài)。MySQL 可以通過(guò) Flush tables with read lock (FTWRL) 實(shí)現(xiàn),PS:unlock tables 可以解除只讀狀態(tài)

執(zhí)行該命令之后,數(shù)據(jù)更新語(yǔ)句(DML)數(shù)據(jù)的增刪改操作以及數(shù)據(jù)操縱語(yǔ)句(DDL)修改表結(jié)構(gòu)等操作將被阻塞。

2.1 全局鎖的應(yīng)用場(chǎng)景

最典型的要數(shù)全庫(kù)邏輯備份,就是把整個(gè)庫(kù)的所有表都 select 出來(lái)存成文本。

假設(shè)現(xiàn)在我的數(shù)據(jù)庫(kù)是讀寫(xiě)分離的:主寫(xiě)從讀。有一種思路是使用 FTWRL 定時(shí)備份 + binlog 恢復(fù)增量數(shù)據(jù)。

用 FTWRL 確保備份期間不會(huì)有其他線程對(duì)數(shù)據(jù)庫(kù)做更新操作,然后整庫(kù)備份。這時(shí)數(shù)據(jù)庫(kù)實(shí)例會(huì)處于只讀狀態(tài),就會(huì)造成兩個(gè)問(wèn)題:

在主庫(kù)備份,備份期間不能寫(xiě)入,業(yè)務(wù)就會(huì)收到嚴(yán)重影響。

在從庫(kù)備份,備份期間不能執(zhí)行主庫(kù)同步的過(guò)來(lái)的 binlog(鎖住了,不能寫(xiě)入),就會(huì)導(dǎo)致主從延遲,業(yè)務(wù)也會(huì)受到影響。

如果非要用這種方式,那么建議是在一個(gè)月黑風(fēng)高,系統(tǒng)最少用戶(hù)在使用的時(shí)候。

2.2 為什么要加鎖?

上面說(shuō)了,利用全局鎖備份會(huì)造成兩個(gè)問(wèn)題。那不加鎖行嗎?廢話(huà),肯定是不行的。不加鎖,你養(yǎng)我呀(備份出問(wèn)題被開(kāi)除)?

不加鎖同樣會(huì)出現(xiàn)意想不到的問(wèn)題:舉個(gè)栗子,看電影買(mǎi)票,系統(tǒng)有個(gè)余額表和用戶(hù)已購(gòu)票表。

為什么要加鎖?

現(xiàn)在我要備份,期間有人買(mǎi)票。邏輯上:余額表減掉相應(yīng)金額,已購(gòu)票表加上一張票。備份就會(huì)出現(xiàn)兩個(gè)問(wèn)題:

先備份余額表,用戶(hù)購(gòu)買(mǎi),再備份用戶(hù)表。這是會(huì)怎樣呢?方便理解,我畫(huà)張圖:

從上圖,我們也大概知道發(fā)生了啥。我來(lái)捋一捋:

T1 時(shí)刻是備份前兩個(gè)表的數(shù)據(jù)狀態(tài);T2 時(shí)刻開(kāi)始備份,只備份了余額表;T3 時(shí)刻,由于沒(méi)有加鎖,用戶(hù)買(mǎi)票;T4 時(shí)刻是買(mǎi)完票后的狀態(tài);T5 時(shí)刻備份到已購(gòu)票表。

看最終的備份狀態(tài)你發(fā)現(xiàn)沒(méi)有???用戶(hù)錢(qián)沒(méi)少,票卻多了一張(用戶(hù)竊喜,程序員苦逼)。

以上就是不加鎖的下場(chǎng),它會(huì)導(dǎo)致數(shù)據(jù)前后不一致。這還是先備份余額表后備份已購(gòu)票表的情況出現(xiàn)的問(wèn)題。

如果,備份的順序顛倒一下就會(huì)出現(xiàn):用戶(hù)錢(qián)少了,票卻沒(méi)增加(你指定被投訴,程序員還是苦逼)。

通過(guò)上面分析知道,不加鎖的話(huà)。備份得到的庫(kù)不是同一個(gè)邏輯時(shí)間點(diǎn),才會(huì)造成這種后果。那怎么保證是同一邏輯時(shí)間點(diǎn)呢?

這時(shí)候就要引入上篇文章提到的一致性視圖。

2.3 一致性視圖備份

上篇說(shuō)到在可重讀隔離級(jí)別下開(kāi)啟一個(gè)事務(wù),會(huì)創(chuàng)建一致性視圖。

你可能會(huì)問(wèn):狗狗你說(shuō)得,我都知道。問(wèn)題是怎么在備份的時(shí)候開(kāi)啟事務(wù)呢?

是這樣,MySQL 自帶的邏輯備份工具是 mysqldump 。它使用參數(shù) -single-transaction 可以啟動(dòng)一個(gè)事務(wù),從而確保拿到一致性視圖。并且由于 MVCC 的支持,備份期間數(shù)據(jù)庫(kù)仍可以寫(xiě)入。比如像這樣:

// 詳細(xì)參數(shù)見(jiàn):cnblogs.com/markLogZhu/p/11398028.html// 格式:mysqldump [選項(xiàng)] --數(shù)據(jù)庫(kù)名 [選項(xiàng) 表名] > 腳本名mysqldump-uroot-p test-single-transaction>/backup/mysqldump/test.db復(fù)制代碼

這時(shí)好學(xué)的朋友可能會(huì)說(shuō):既然有了這功能,那不用 FTWRL 命令行不行呀?

答案是:可以的,前提是你的數(shù)據(jù)庫(kù)引擎要支持可重復(fù)讀隔離級(jí)別,比如:InnDB;如果是 MyLSAM,那么很抱歉,你還是得用 FTWRL,不然備份拿到的視圖還是不一致。就會(huì)出現(xiàn)上面數(shù)據(jù)不一致的問(wèn)題。

2.4 readonly = 1 的方式行么?

提到全庫(kù)只讀你可能想到這個(gè)命令:

mysql>setglobalread_only=1;復(fù)制代碼

能使用它來(lái)讓全庫(kù)只讀么?不行或者說(shuō)是不建議,主要原因有三點(diǎn):

影響業(yè)務(wù)邏輯;set global read_only=1 可能會(huì)用于一些業(yè)務(wù)判斷,比如:主從的判斷,從庫(kù)只讀。

異常不釋放狀態(tài);FTRWL 命令在異常發(fā)生時(shí),會(huì)自動(dòng)釋放全局鎖;而 set global read_only=1 在異常時(shí),數(shù)據(jù)庫(kù)會(huì)一直保持只讀狀態(tài),這時(shí)候業(yè)務(wù)就完?duì)僮恿恕?/p>

set global read_only=1 這個(gè)命令對(duì)超級(jí)管理員角色無(wú)效;備份期間,超管更新數(shù)據(jù)庫(kù)還是會(huì)導(dǎo)致數(shù)據(jù)不一致問(wèn)題。

03 表級(jí)鎖

MySQL 有兩種表級(jí)鎖:表鎖以及元數(shù)據(jù)鎖(meta data lock,MDL)

3.1 表鎖

表鎖的語(yǔ)法是這樣的:lock tables ... read/write,它是顯式使用的,同樣也是通過(guò) unlock tables 主動(dòng)釋放鎖;當(dāng)然,客戶(hù)算斷開(kāi)或者異常時(shí)也會(huì)釋放

mysql>locktables student read,course read;mysql>SELECTcount(1)FROMstudent;mysql>SELECTcount(1)FROMcourse;mysql>unlock tables;復(fù)制代碼

需要注意一點(diǎn):lock tables 除了會(huì)限制別的線程讀寫(xiě)以外,也限定了本線程接下來(lái)操作的對(duì)象。舉個(gè)栗子:

線程 A 執(zhí)行 lock tables student read,course write; 語(yǔ)句,其他線程讀 student、讀寫(xiě) course 都會(huì)被阻塞。同時(shí),線程 A 在執(zhí)行 unlock tables 之后,也只能讀 student、讀寫(xiě) course;不能訪問(wèn)其他表。整個(gè)表格更直觀:

student 表course 表其他表

線程A讀讀寫(xiě)不允許

其他線程阻塞阻塞隨便

PS:在沒(méi)有更細(xì)粒度的年代,表鎖是最常用與處理并發(fā)的方式。但是對(duì)于 InnDB 來(lái)說(shuō),一般不使用 lock tables 控制并發(fā),因?yàn)榱6忍罅恕?/p>

3.2 MDL 元數(shù)據(jù)鎖

MDL 不需要我們記命令,它是隱式使用的,訪問(wèn)表會(huì)自動(dòng)加上。它的主要作用是防止 DDL(改表結(jié)構(gòu)) 和 DML(CRUD 表數(shù)據(jù)) 并發(fā)的沖突。

舉個(gè)栗子,線程 A 遍歷查詢(xún)表數(shù)據(jù),這期間線程 B 刪了表的某一列,這時(shí) A 拿到的數(shù)據(jù)就跟表結(jié)構(gòu)對(duì)不上,MySQL 不允許這種事發(fā)生,所以在 5.5 版本引入了 MDL。

它的邏輯很簡(jiǎn)單,對(duì)表進(jìn)行 CRUD 操作,加 MDL 讀鎖;對(duì)表結(jié)構(gòu)下手時(shí),加 MDL 寫(xiě)鎖。因此:

讀讀不互斥,可以多線程對(duì)一張表增刪改查。

讀寫(xiě)互斥、寫(xiě)寫(xiě)互斥,保證對(duì)表結(jié)構(gòu)下手時(shí)只能有一個(gè)線程操作,另一個(gè)進(jìn)入阻塞。

3.2.1 加個(gè)字段就搞掛數(shù)據(jù)庫(kù)?

我們知道 MDL 默認(rèn)是系統(tǒng)加的,對(duì)表結(jié)構(gòu)下手時(shí)(加字段、該字段、加索引等等),需要全表掃描。對(duì)大表操作時(shí),你肯定會(huì)選月黑鳳高,系統(tǒng)使用人數(shù)最少時(shí)進(jìn)行,以免遭投訴。

但不只是大表,有時(shí)候?qū)π”磉M(jìn)行操作時(shí),也會(huì)有這樣的問(wèn)題。比如下面的例子:4 個(gè)session 對(duì)表進(jìn)行操作。

PS:版本是 MySQL 5.7

加個(gè)字段

前提:注意,我這里的事務(wù)是手動(dòng)開(kāi)啟和提交的。而 MDL 鎖是語(yǔ)句開(kāi)始時(shí)申請(qǐng),事務(wù)提交才釋放。所以,如果是自動(dòng)提交就不會(huì)出現(xiàn)下面的問(wèn)題。

T1、T2 時(shí)刻 session A 事務(wù)啟動(dòng),加個(gè) MDL 讀鎖,然后執(zhí)行 select 語(yǔ)句。注意:這時(shí)事務(wù)并沒(méi)有提交;

T3 時(shí)刻 session B 也是讀操作,可以共享 MDL 讀鎖,順利執(zhí)行;

T4 時(shí)刻 session C 不講武德,對(duì)表執(zhí)行 DDL (改表結(jié)構(gòu))操作,需要的是 MDL 寫(xiě)鎖,所以被阻塞;

T5 時(shí)刻 session D 也是讀操作,按道理說(shuō) session C 阻塞應(yīng)該沒(méi)影響。

但是 MySQL 有一個(gè)隊(duì)列會(huì)根據(jù)時(shí)間先后決定哪個(gè) Session 先執(zhí)行。所以,不管是 D 還是之后的 session 都會(huì)被 C 阻塞。而恰巧 student 又是訪問(wèn)頻率很高的表,如此這個(gè)庫(kù)的線程數(shù)很快就打滿(mǎn)了。

此時(shí),數(shù)據(jù)庫(kù)完全不能讀寫(xiě),甚至導(dǎo)致宕機(jī),在用戶(hù)界面看來(lái)就是沒(méi)響應(yīng)了。

3.2.2 安全地更改表

相信你都看出來(lái)了,出現(xiàn)上面問(wèn)題是因?yàn)槭褂昧碎L(zhǎng)事務(wù)(一個(gè)事務(wù)包括 session A、B、C、D 的操作)。事務(wù)一直不提交,MDL 鎖就會(huì)一直被占用。

所以,遇到這種情況就要在 MySQL 的 information_schema 表中先找出長(zhǎng)事務(wù)對(duì)應(yīng)的線程,把它 kill 掉。

// MySQL 長(zhǎng)事務(wù)請(qǐng)看這篇:cnblogs.com/mysqljs/p/11552646.html// 查詢(xún)事務(wù)select*frominformation_schema.INNODB_TRX;復(fù)制代碼

那你可能又問(wèn)了。我的表就是熱點(diǎn)表訪問(wèn)很高頻,但我又不得不加個(gè)字段。那應(yīng)該咋辦呢?回想下多線程業(yè)務(wù)操作時(shí),線程一直拿不到鎖,我們是怎么處理的?

沒(méi)錯(cuò),就是加超時(shí)時(shí)間。比如在 alter 語(yǔ)句里面加個(gè)等待時(shí)間,超過(guò)了這時(shí)間還拿不到鎖。也不要阻塞后面的業(yè)務(wù)查詢(xún)語(yǔ)句,先放棄更改。之后再交由你司 DBA 重復(fù)這個(gè)過(guò)程,直到更改成功。加等待時(shí)間語(yǔ)句,像下面這樣的:

// N 以秒為單位ALTERTABLEtbl_nameWAITNaddcolumn...復(fù)制代碼

04 行鎖

mysql 的行索是在引擎實(shí)現(xiàn)的,但并不是所有引擎都支持行鎖,不支持行鎖的引擎只能使用表鎖。

行鎖比較容易理解:行鎖就是針對(duì)數(shù)據(jù)表中行記錄的鎖。比如:事務(wù) A 先更新一行,同時(shí)事務(wù) B 也要更新同一行,則必須等事務(wù) A 的操作完成后才能進(jìn)行更新。

4.1 兩階段提交

先舉個(gè)栗子:事務(wù) A 和 B 對(duì) student 中的記錄進(jìn)行操作。

兩階段提交

其中事務(wù) A 先啟動(dòng),在這個(gè)事務(wù)中更新兩條數(shù)據(jù);事務(wù) B 后啟動(dòng),更新 id = 1 的數(shù)據(jù)。由于 A 更新的也是 id = 1 的數(shù)據(jù),所以事務(wù) B 的 update 語(yǔ)句從事務(wù) A 開(kāi)始就會(huì)被阻塞,直到事務(wù) A 執(zhí)行 commit 之后,事務(wù) B 才能繼續(xù)執(zhí)行

在事務(wù)期間,事務(wù) A 實(shí)際上持有 id = 1 和 id = 2 這兩行的行鎖。如果事務(wù) B 更新的是 id = 2 的數(shù)據(jù),那么它阻塞的時(shí)間就是從 A 更新 id = 2 這行開(kāi)始(事務(wù) A 更新 id = 1 時(shí),它并沒(méi)有阻塞),到事務(wù) A 提交結(jié)束,比更新 id = 1 數(shù)據(jù)阻塞的時(shí)間要短。PS:理解這句話(huà)很重要。

在 InnoDB 事務(wù)中,行鎖是在需要的時(shí)候才加上的,但并不是不需要了就立刻釋放,而是要等到事務(wù)結(jié)束時(shí)才釋放。這個(gè)就是兩階段鎖協(xié)議。鎖的添加與釋放分到兩個(gè)階段進(jìn)行,之間不允許交叉加鎖和釋放鎖。

根據(jù)這個(gè)特性,對(duì)于高并發(fā)的行記錄的操作語(yǔ)句就可以盡可能的安排到最后面,以減少鎖等待的時(shí)間,提高并發(fā)性能。

舉個(gè)栗子:廣州長(zhǎng)隆樂(lè)園賣(mài)票系統(tǒng)。賣(mài)出一張票的邏輯應(yīng)該分三步:

1、扣除用戶(hù)賬戶(hù)余額

2、增加長(zhǎng)隆賬戶(hù)收入

3、插入一條交易記錄

三個(gè)操作必須是要放在同一個(gè)事務(wù)當(dāng)中,那應(yīng)該怎么安排它們的執(zhí)行順序呢?做個(gè)分析:

用戶(hù)余額表是個(gè)人的,并發(fā)度很低;

長(zhǎng)隆賬戶(hù)表每個(gè)用戶(hù)買(mǎi)票都要訪問(wèn),并發(fā)度最高;

交易記錄表是插入操作問(wèn)題不大;

這時(shí)將事務(wù)步驟安排成 3、1、2 這樣的順序是最佳的。因?yàn)榇藭r(shí)如果有別的用戶(hù)買(mǎi)票,它的事務(wù)在順序 1、2 并不會(huì)阻塞,而是到了順序 3 更新長(zhǎng)隆賬戶(hù)表才會(huì)引起阻塞。但它的阻塞時(shí)間是最短的。

4.2 死鎖

不同線程出現(xiàn)循環(huán)資源依賴(lài),涉及的線程都在等待別的線程釋放資源時(shí),就會(huì)導(dǎo)致這幾個(gè)線程都進(jìn)入無(wú)限等待的狀態(tài),稱(chēng)為死鎖。

舉個(gè)行鎖死鎖的例子:兩個(gè)事物相互等待對(duì)方持有的鎖。

死鎖

操作開(kāi)始,事務(wù) A 持有 id = 1 的行鎖,事務(wù) B 持有 id = 2 的行鎖;事務(wù) A 想更新 id = 2 行數(shù)據(jù),不料事務(wù) B 已持有,事務(wù) A 只能等待事務(wù) B 釋放 id = 2 的行鎖;同理,事務(wù) B 想更新 id = 1 行數(shù)據(jù),不料事務(wù) A 已持有,事務(wù) B 只能等事務(wù) A 釋放 id = 1 的行鎖

兩者互相等待,一直到完?duì)僮印_@就是死鎖,懂了么?

4.3 如何解決死鎖?

那出現(xiàn)了死鎖怎么辦?有兩個(gè)解決策略:

進(jìn)入等待,直到超時(shí)

進(jìn)行死鎖檢測(cè),主動(dòng)回滾某個(gè)事務(wù)

4.2.2 加入等待時(shí)間

首先是第一種:直接進(jìn)入等待,直到超時(shí)。這個(gè)超時(shí)時(shí)間可以通過(guò)參數(shù) innodb_lock_wait_timeout 設(shè)置。 這個(gè)參數(shù),默認(rèn)設(shè)置的鎖等待時(shí)間是 50s

在 MySQL 中,像下面這樣執(zhí)行即可:

// 設(shè)置等待時(shí)間mysql>setglobalinnodb_lock_wait_timeout=500;復(fù)制代碼

上面這個(gè)語(yǔ)句表示:當(dāng)出現(xiàn)死鎖以后,第一個(gè)被鎖住的線程要過(guò) 500s 才會(huì)超時(shí)退出,然后其他線程才有可能繼續(xù)執(zhí)行。

你可能說(shuō)這不解決啦?真簡(jiǎn)單。別得意,這里還有個(gè)坑。到底設(shè)置多長(zhǎng)的過(guò)期時(shí)間合適呢?

我設(shè)置 1s 吧,有些線程可能并沒(méi)有發(fā)生死鎖,只是正常的等待鎖。這就會(huì)造成本來(lái)正常的線程讓我給干掉了。

4.2.3 死鎖檢測(cè)

再看第二種:死鎖檢測(cè),主動(dòng)回滾某個(gè)事務(wù)。MySQL 通過(guò)設(shè)置 innodb_deadlock_detect 的值決定是否開(kāi)啟檢測(cè),默認(rèn)值是 on(開(kāi)啟)。

主動(dòng)死鎖檢測(cè)在發(fā)生死鎖的時(shí)候,可以快速發(fā)現(xiàn)并進(jìn)行處理的,但是它也有額外負(fù)擔(dān)。

什么負(fù)擔(dān)呢?循環(huán)依賴(lài)檢測(cè),過(guò)程如下圖:

新來(lái)的線程 F,被鎖了后就要檢查鎖住 F 的線程(假設(shè)為 D)是否被鎖,如果沒(méi)有被鎖,則沒(méi)有死鎖,如果被鎖了,還要查看鎖住線程 D 的是誰(shuí),如果是 F,那么肯定死鎖了,如果不是 F(假設(shè)為 B),那么就要繼續(xù)判斷鎖住線程 B 的是誰(shuí),一直走知道發(fā)現(xiàn)線程沒(méi)有被鎖(無(wú)死鎖)或者被 F 鎖?。ㄋ梨i)才會(huì)終止

如果大量并發(fā)修改同一行數(shù)據(jù),死鎖檢測(cè)又會(huì)怎樣呢?

假設(shè)有 1000 個(gè)并發(fā)線程同時(shí)更新同一行,那么死鎖檢測(cè)操作就是 1000 x 1000 達(dá)到 100 萬(wàn)量級(jí)的。即便最終檢測(cè)結(jié)果沒(méi)有死鎖,但這期間要消耗大量 CPU 資源。所以,你就會(huì)看到 CPU 利用率很高,但是每秒?yún)s執(zhí)行不了幾個(gè)事務(wù)的情況

4.2.4 解決熱點(diǎn)行更新問(wèn)題

那前面兩種方案都有弊端,死鎖的問(wèn)題應(yīng)該怎么解決呢?

一種比較依賴(lài)運(yùn)氣的方法就是:如果你能確保這個(gè)業(yè)務(wù)一定不會(huì)出現(xiàn)死鎖,可以臨時(shí)把死鎖檢測(cè)關(guān)掉。但是這可能會(huì)影響到業(yè)務(wù):開(kāi)啟死鎖檢測(cè),出現(xiàn)死鎖就回滾重試,不會(huì)影響到業(yè)務(wù)。如果關(guān)閉,可能就會(huì)大量超時(shí),嚴(yán)重就會(huì)拖垮數(shù)據(jù)庫(kù)。

另一種就是在服務(wù)端(消息隊(duì)列或者數(shù)據(jù)庫(kù)服務(wù)端)控制并發(fā)度:之所以擔(dān)心死鎖檢測(cè)會(huì)造成額外的負(fù)擔(dān),是因?yàn)椴l(fā)線程很多的時(shí)候,假設(shè)我們能在服務(wù)端做下限流,比如同一樣最多只能允許 10 個(gè)線程同時(shí)修改。

一個(gè)思想:減少死鎖的主要方向,就是控制訪問(wèn)相同資源的并發(fā)事務(wù)量。

05 巨人的肩膀

《高性能 MySQL》

06 總結(jié)

本文詳細(xì)介紹了 MySQL 的全局鎖、表級(jí)鎖、元數(shù)據(jù)鎖以及行鎖和死鎖。其中全局鎖撩到了應(yīng)用場(chǎng)景、為什么備份要加全局鎖?如何利用一致性視圖備份以及為啥 readonly = 1 不適合用來(lái)做備份?

表級(jí)鎖聊了表鎖、MDL 元數(shù)據(jù)鎖以及怎么利用 MDL 鎖安全快速更改表結(jié)構(gòu);行鎖聊了兩階段提交、死鎖的定義、死鎖的檢測(cè)以及給怎么解決死鎖,提供了兩種思路。

好啦,以上就是狗哥關(guān)于 MySQL 鎖的總結(jié)。感謝各技術(shù)社區(qū)大佬們的付出,尤其是極客時(shí)間,真的牛逼。如果說(shuō)我看得更遠(yuǎn),那是因?yàn)槲艺驹谀銈兊募绨蛏?。希望這篇文章對(duì)你有幫助,我們下篇文章見(jiàn)~

作者:Java斗帝之路

鏈接:http://www.itdecent.cn/p/12791245c3b9

來(lái)源:簡(jiǎn)書(shū)

著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

?著作權(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ù)。

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

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