mysql lock_MySQL-锁总结
鎖
鎖機制用于管理對共享資源的并發(fā)訪問。
lock和latch
在數(shù)據(jù)庫中,lock和Latch都稱為鎖,但是兩者意義不同。
latch稱為閂鎖(shuang suo),其要求鎖定的時間必須非常短。若持續(xù)的時間長,則應用的性能會非常差。在InnoDB存儲引擎中,latch又分為mutex互斥鎖 和 rwLock讀寫鎖。其目的是為了保證并發(fā)線程操作臨界資源的正確性。通常沒有死鎖的檢測機制。
lock的對象是事務,用來鎖定的是數(shù)據(jù)庫中的對象,如表、頁、行。并且一般lock的對象僅在事務commit或者rollback后進行釋放。有死鎖檢測機制。
通過show engine innodb mutex可以查看InnoDB存儲引擎的中l(wèi)atch,具體字段詳情如下表:
鎖的類型
有幾個索引,需要分別向索引加鎖。
共享鎖、排他鎖
InnoDB存儲引擎實現(xiàn)了如下兩種標準的行級鎖:
共享鎖(S Lock):允許事務讀一行數(shù)據(jù)
排他鎖(X Lock):允許事務刪除 或 更新一行數(shù)據(jù)
如果一個事務T1已經(jīng)獲取了行r的共享鎖,那么另外的事務T2可以立即獲得行r的共享鎖。因為讀取并不會改變行的數(shù)據(jù),所以可以多個事務同時獲取共享鎖,稱這種情況為鎖兼容。但若有其他的事務T3想獲得行R的排他鎖,則其必須等待事務T1、T2釋放行r上面的共享鎖,稱這種情況為鎖不兼容。下面顯示了共享鎖和排他鎖的兼容性:
從表6-3可以看出X鎖與任何鎖都不兼容,而S鎖僅和S鎖兼容。S鎖和X鎖都是行鎖,兼容是指對同一行記錄鎖的兼容情況。
普通 select 語句默認不加鎖,而CUD操作默認加排他鎖。
記錄鎖
Record Lock,僅鎖定一行記錄(如共享鎖、排他鎖)
記錄鎖總是會去鎖定索引記錄,如果表在建立的時候,沒有設置任何一個索引,那么InnoDB會使用隱式的主鍵來進行鎖定。
查詢條件的列是唯一索引的情況下,臨建鎖退化為記錄鎖
間隙鎖
Gap Lock,鎖定一個范圍,但不包含記錄本身。
關閉間隙鎖的2種方式:
(1)將事務隔離級別變?yōu)閞ead committed
(2)將參數(shù)innodb_locks_unsafe_for_binlog設置為1
在上述配置下,除了外鍵和唯一性檢查依然需要間隙鎖,其余情況僅適用行鎖進行鎖定。
臨鍵鎖
Next-Key Lock,等于記錄鎖 + 臨鍵鎖,鎖定一個范圍,并且鎖定記錄本身。主要是阻止多個事務將記錄插入到同一個范圍內(nèi),從而避免幻讀。
假如一個索引有10、11、13、20這四個值,那么該索引可能被鎖定的區(qū)間為:
若事務T1已經(jīng)通過臨鍵鎖鎖定了如下范圍:
當插入新的記錄12時,則鎖定的范圍變成:
當查詢的索引是唯一索引的時候,InnoDB會將臨鍵鎖優(yōu)化成記錄鎖,從而提高并發(fā)。這時候,將不再由間隙鎖避免幻讀的問題,但是試驗了下,即使優(yōu)化成記錄鎖,也不會有幻讀的問題,其實是因為MVCC,在可重復讀的情況下,SELECT操作只會查找行版本號小于當前事務版本號的記錄,其他事務(事務開啟時間比當前事務晚)新插入的記錄版本號不滿足條件,就不會查出來。
對于輔助索引,當執(zhí)行類似select * from z where b = 3 for update;加鎖語句時,會加上臨鍵鎖,并且下一個鍵值的范圍也會加上間隙鎖。
值得注意的是,對于唯一鍵值的鎖定,由臨鍵鎖優(yōu)化為記錄鎖,僅存在于查詢所有的唯一索引。若唯一索引由多列組成,而查詢僅是查找多個唯一索引中的一個,那么查詢其實是range類型查詢,而不是point類型查詢,故InnoDB存儲引擎還是繼續(xù)使用臨鍵鎖。
在InnoDB存儲引擎中,通過使用臨鍵鎖來避免不可重復讀的問題(即幻讀)。在使用臨鍵鎖的情況下,對于索引的掃描,不僅僅鎖住掃描的到索引,而且還鎖住這些索引覆蓋的范圍。因此,在這些范圍內(nèi)插入都是不允許的。這樣子就避免了其他事務在這些范圍內(nèi)插入數(shù)據(jù)導致不可重復讀的問題。
意向鎖
概念:未來的某個時刻,事務可能要加共享/排它鎖了,先提前聲明一個意向
意向鎖有這樣一些特點:
(1)意向鎖是表級別的鎖
(2)意向鎖分為:
意向共享鎖(intention shared lock, IS),它預示著,事務有意向?qū)Ρ碇械哪承┬屑庸蚕鞸鎖
意向排它鎖(intention exclusive lock, IX),它預示著,事務有意向?qū)Ρ碇械哪承┬屑优潘黊鎖
(3)意向鎖協(xié)議:
事務要獲得某些行的共享鎖,必須先獲得表的意向共享鎖IS
事務要獲取某些行的排他鎖,必須先獲得表的意向排他鎖IX
(4)由于意向鎖僅僅表明意向,它其實是比較弱的鎖,意向鎖之間并不相互互斥,而是可以并行,其兼容互斥表如下:
? IS IX
IS 兼容 兼容
IX 兼容 兼容
(5)既然意向鎖之間都相互兼容,那其意義在哪里呢?它會與共享鎖/排它鎖互斥,其兼容互斥表如下:
? S X
IS 兼容 互斥
IX 互斥 互斥
(排它鎖是很強的鎖,不與其他類型的鎖兼容。這也很好理解,修改和刪除某一行的時候,必須獲得強鎖,禁止這一行上的其他并發(fā),以保障數(shù)據(jù)的一致性。)
InnoDB支持多粒度鎖定,這種鎖定允許事務在行級上的鎖和表級上的鎖同時存在。為了支持不同粒度上進行加鎖操作,InnoDB存儲引擎支持一種額外的鎖方式,稱之為意向鎖。意向鎖是將鎖定的對象分為多個層次,意向鎖意味著事務希望在更細的粒度上進行加鎖。如圖6-3所示:
若將上鎖的對象看成一棵樹,那么對最下層的對象上鎖,也就是對最細粒度的對象上鎖,那么首先需要對粗粒度的對象進行上鎖。如上圖,如果需要對頁上的記錄上X鎖,那么需要分別對數(shù)據(jù)庫A、表、頁 上意向鎖IX,最后對記錄r上排他鎖X。
若其中任何一部分導致等待,那么該操作需要等待粗粒度鎖的完成。舉例來說,事務T1在對記錄r加X鎖之前,已有事務T2對表1進行了S表鎖,那么表1上面已經(jīng)存在S鎖,之后事務T1試圖在表1上加IX鎖(獲取記錄r的X鎖必須先獲取表1的IX鎖),由于不兼容,所以事務T1需要等待事務T2釋放表鎖。
插入意向鎖
對已有數(shù)據(jù)行的修改與刪除,必須加強互斥鎖X鎖,那對于數(shù)據(jù)的插入,是否還需要加這么強的鎖,來實施互斥呢?插入意向鎖,孕育而生。
插入意向鎖,是間隙鎖(Gap Locks)的一種(所以,也是實施在索引上的),它是專門針對insert操作的。
它的用處是:多個事務,在同一個索引,同一個范圍區(qū)間插入記錄時,如果插入的位置不沖突,不會阻塞彼此。
示例
在MySQL,InnoDB,RR下:
t(id unique PK, name);
數(shù)據(jù)表中有數(shù)據(jù):
10, shenjian
20, zhangsan
30, lisi
事務A先執(zhí)行,在10與20兩條記錄中插入了一行,還未提交:
insert into t values(11, xxx);
事務B后執(zhí)行,也在10與20兩條記錄中插入了一行:
insert into t values(12, ooo);
(1)會使用什么鎖?
(2)事務B會不會被阻塞呢?
回答:雖然事務隔離級別是RR,雖然是同一個索引,雖然是同一個區(qū)間,但插入的記錄并不沖突,故這里:
使用的是插入意向鎖
并不會阻塞事務B
自增鎖
自增鎖是MySQL一種特殊的鎖,如果表中存在自增字段,MySQL便會自動維護一個自增鎖。
在InnoDB存儲引擎的內(nèi)存結(jié)構(gòu)中,對每個含有自增長值的表都有一個自增長計數(shù)器。當對含有自增長計數(shù)器的表進行插入操作時,這個這個計數(shù)器會被初始化,執(zhí)行如下操作來得到計數(shù)器的值:
select max(auto_inc_col) from t for update
插入操作會依據(jù)這個自增長的計數(shù)器值加1賦予自增長列。這個實現(xiàn)方式成為Auto-Inc Locking。這種鎖其實是采用一種表鎖的機制,為了提高插入的性能,鎖不是在一個事務完成以后才釋放,而是在完成對自增長值插入的SQL語句后立即釋放。
雖然Auto-Inc Locking從一定程度上提高了并發(fā)插入的效率,但還是存在一些性能上的問題。對于有自增長值的列的并發(fā)插入性能較差,事務必須等待前一個插入的完成(雖然不用等待事務的完成)。
從MySQL5.12版本開始,InnoDB存儲引擎提供了一種輕量級互斥量的自增長實現(xiàn)方式。這種方式大大提高了自增長值插入的性能。并且從該版本開始,InnoDB存儲引起提供了一個參數(shù)innodb_innodb_autoinc_lock_mode來控制自增長模式,該參數(shù)的默認值為1。首先看下自增長的插入分類,如下圖:
下圖展示了innodb_innodb_autoinc_lock_mode的不同值對自增的影響:(值為1、2的時候,看不懂。。)
InnoDB存儲引擎中自增長的實現(xiàn)和MyISAM不同。MyISAM存儲引擎是表鎖設計,自增長不用考慮并發(fā)插入的問題。在InnoDB存儲引擎中,自增長值的列必須是索引,同時必須是索引的第一個列,如果不是第一個列,則MySQL會拋出異常。MyISAM存儲引擎沒有這個問題。
外鍵與鎖
如果沒有為外鍵顯示添加索引,InnoDB自動為外鍵創(chuàng)建索引,這樣子避免表鎖。
對于外鍵值的插入或更新,首先需要查詢父表中的記錄,即select父表。但是不是使用一致性非鎖定讀,因為這樣子會發(fā)生數(shù)據(jù)不一致的問題。因此這時使用的是select…lock in share mode,即主動對父表加一個共享鎖。如果這時父表已經(jīng)加了X鎖,子表上面的操作將會被阻塞,如下圖:
MVCC多版本
又稱為一致性非鎖定讀。指InnoDB通過行多版本控制的方式來讀取當前執(zhí)行時間數(shù)據(jù)庫中行的數(shù)據(jù)。如果讀取的行正在執(zhí)行delete或者update操作,這時讀操作不會因此去等待行上鎖的釋放。相反的,InnoDB存儲引擎會去讀取行的一個快照數(shù)據(jù)。
在默認配置下,即事務的隔離界別為REPEATABLE READ(可重復讀)模式下,InnoDB存儲引擎的SELECT操作使用一致性非鎖定讀。
快照數(shù)據(jù)是指該行的之前版本的數(shù)據(jù),該實現(xiàn)是通過undo段來完成。而undo用來在事務中回滾數(shù)據(jù),因此快照數(shù)據(jù)本身是沒有額外的開銷。此外讀取快照數(shù)據(jù)是不需要上鎖的,因為沒有事務需要對歷史的數(shù)據(jù)進行修改操作。
非鎖定度機制極大的提高了數(shù)據(jù)庫的并發(fā)性。這是InnoDB默認的讀取方式,即讀取不會占用表上的鎖。但是在不同事務隔離界別下,讀取的方式不同,并不是在每個事務隔離界別下都是采用非鎖定的一致性讀。此外,即使都是使用非鎖定的一致性讀,但是對于快照數(shù)據(jù)的定義也是各不相同。
快照數(shù)據(jù)其實就是當前行數(shù)據(jù)之前的歷史版本,每行記錄可能有多個版本。一個行記錄可能有不止一個快照數(shù)據(jù),一般稱這種技術(shù)為行多版本技術(shù),由此帶來的并發(fā)控制,稱之為多版本并發(fā)控制 MVCC。
在事務隔離界別read committed 和 repeatable read(InnoDB默認的事務隔離界別)下,InnoDB使用非鎖定一致性讀。然而,對于快照數(shù)據(jù)的定義卻不相同。對于快照數(shù)據(jù),非一致性讀總是讀取被鎖定行的最新一份快照數(shù)據(jù)(如果沒有被鎖定,則讀取行的最新數(shù)據(jù);如果行鎖定了,則讀取該行的最新一個快照)。而在repeatable read事務隔離級別下,對于快照數(shù)據(jù),非一致性讀總是讀取事務開始時的行數(shù)據(jù)版本。
MVCC的優(yōu)缺點
MVCC在大多數(shù)情況下代替了行鎖,實現(xiàn)了對讀的非阻塞,讀不加鎖,讀寫不沖突。缺點是每行記錄都需要額外的存儲空間,需要做更多的行維護和檢查工作。注意寫寫不能并行。
MVCC的實現(xiàn)原理
undo log
undo log是為回滾而用,具體內(nèi)容就是copy事務前的數(shù)據(jù)庫內(nèi)容(行)到undo buffer,在適合的時間把undo buffer中的內(nèi)容刷新到磁盤。undo buffer與redo buffer一樣,也是環(huán)形緩沖,但當緩沖滿的時候,undo buffer中的內(nèi)容會也會被刷新到磁盤;與redo log不同的是,磁盤上不存在單獨的undo log文件,所有的undo log均存放在主ibd數(shù)據(jù)文件中(表空間),即使客戶端設置了每表一個數(shù)據(jù)文件也是如此。在Innodb中,undo log被劃分為多個段,具體某行的undo log就保存在某個段中,稱為回滾段。可以認為undo log和回滾段是同一意思。
為了便于理解MVCC的實現(xiàn)原理,這里簡單介紹一下undo log的工作過程
在不考慮redo log 的情況下利用undo log工作的簡化過程為:
序號 動作
1 開始事務
2 記錄數(shù)據(jù)行數(shù)據(jù)備份到undo log
3 更新數(shù)據(jù)
4 將undo log寫到磁盤
5 將數(shù)據(jù)寫到磁盤
6 提交事務
(1)為了保證數(shù)據(jù)的持久性,數(shù)據(jù)要在事務提交之前持久化
(2)undo log的持久化必須在在數(shù)據(jù)持久化之前,這樣才能保證系統(tǒng)崩潰時,可以用undo log來回滾事務
(3)Innodb通過undo log保存了已更改行的舊版本的快照。
redo log
redo log就是保存執(zhí)行的SQL語句到一個指定的Log文件,當MySQL執(zhí)行recovery(修復)時重新執(zhí)行redo log記錄的SQL操作即可。當客戶端執(zhí)行每條SQL(更新語句)時,redo log會被首先寫入log buffer;當客戶端執(zhí)行COMMIT命令時,log buffer中的內(nèi)容會被視情況刷新到磁盤。redo log在磁盤上作為一個獨立的文件存在,即Innodb的log文件。
Innodb中的隱藏列
InnoDB的內(nèi)部實現(xiàn)中為每一行數(shù)據(jù)增加了三個隱藏列用于實現(xiàn)MVCC。
列名
長度(字節(jié))
作用
DB_TRX_ID
6
插入或更新行的最后一個事務的事務標識符。(刪除視為更新,將其標記為已刪除)
DB_ROLL_PTR
7
寫入回滾段的撤消日志記錄(若行已更新,則撤消日志記錄包含在更新行之前重建行內(nèi)容所需的信息)
DB_ROW_ID
6
行標識(隱藏單調(diào)自增id)
一行記錄的結(jié)構(gòu)如下:
數(shù)據(jù)列
..
DB_ROW_ID
DB_TRX_ID
DB_ROLL_PTR
MVCC工作過程
MVCC只在READ COMMITED 和 REPEATABLE READ 兩個隔離級別下工作。READ UNCOMMITTED總是讀取最新的數(shù)據(jù)行,而不是符合當前事務版本的數(shù)據(jù)行。而SERIALIZABLE 則會對所有讀取的行都加鎖。另外事務的版本號是遞增的。
SELECT
InnoDB 會根據(jù)兩個條件來檢查每行記錄:
InnoDB只查找版本(DB_TRX_ID)早于當前事務版本的數(shù)據(jù)行(行的系統(tǒng)版本號<=事務的系統(tǒng)版本號,這樣可以確保數(shù)據(jù)行要么是在開始之前已經(jīng)存在了,要么是事務自身插入或修改過的)
行的刪除版本號(DB_ROLL_PTR)要么未定義(未更新過),要么大于當前事務版本號(在當前事務開始之后更新的)。這樣可以確保事務讀取到的行,在事務開始之前未被刪除。
INSERT
InnoDB為新插入的每一行保存當前系統(tǒng)版本號作為行版本號
DELETE
InnoDB為刪除的每一行保存當前的系統(tǒng)版本號作為行刪除標識
UPDATE
innodb為插入一行新紀錄,保存當前系統(tǒng)版本號作為行版本號,同時保存當前系統(tǒng)版本號到原來的行作為行刪除標示。
MVCC插入示例
F1~F6是字段名稱,1~6是對應的數(shù)據(jù)。后面3個隱藏字段分別對應行ID、事務ID、回滾指針。
初始狀態(tài)
假如有一條新增的數(shù)據(jù),可以認為行ID為1,其他兩個字段為空。
事務1更改該行的值
當事務1更改該行的值時,會進行如下操作:
用排他鎖鎖定該行
記錄redo log
把該行修改前的值復制到undo log,即上圖中下面的行
修改當前的行的值,填寫事務編號,使回滾指針指向undo log中修改的行
釋放鎖
事務2更改該行的值
與事務1相同,此時undo log中有2條記錄,并且通過回滾指針連在一起。
因此,如果undo log一直不刪除,則可以通過當前記錄的回滾指針回溯到該行創(chuàng)建時的初始內(nèi)容,所幸的是在InnoDB中存在清理線程,它會查詢比現(xiàn)在最老的事務還早的undo log,并刪除它們,從而保證undo log文件不會無限增長。
事務提交
當事務正常提交時,InnoDB只需要更改事務狀態(tài)為COMMIT即可,不需要做其他額外的工作,而回滾則復雜一點,需要根據(jù)回滾指針找出事務修改前的版本,并且恢復。如果事務影響的行非常多,回滾則可能變得效率不高。
一致性非鎖定讀(見共享鎖、排他鎖)
在某些情況下,用戶需要顯式的對數(shù)據(jù)庫讀取操作進行加鎖以保證數(shù)據(jù)邏輯的一致性。而這要求數(shù)據(jù)庫支持加鎖語句。InnoDB存儲引擎對于SELECT語句支持兩種一致性的鎖定度操作:
select … for update
共享鎖(S鎖, share locks)。其他事務可以讀取數(shù)據(jù),但不能對該數(shù)據(jù)進行修改,直到所有的共享鎖被釋放。
? 如果事務對某行數(shù)據(jù)加上共享鎖之后,可進行讀寫操作;其他事務可以對該數(shù)據(jù)加共享鎖,但不能加排他鎖,且只能讀數(shù)據(jù),不能修改數(shù)據(jù)。
select … lock in share mode
? 如果事務對數(shù)據(jù)加上排他鎖之后,則其他事務不能對該數(shù)據(jù)加任何的鎖。獲取排他鎖的事務既能讀取數(shù)據(jù),也能修改數(shù)據(jù)。
select…for update對讀取的行記錄加一個X鎖,其他事務不能對已鎖定的行加上任何鎖。select…lock in share mode對讀取的行記錄加一個S鎖,其他事務可以向被鎖定的行加S鎖,但是如果加X鎖,則會被阻塞。
對于一致性非鎖定讀,即時讀取的行已經(jīng)被執(zhí)行了select..for update,也是可以進行讀取的。
如果不加篩選條件(或者篩選條件不走索引),會升級為表鎖
索引數(shù)據(jù)重復率太高會導致全表掃描:當表中索引字段數(shù)據(jù)重復率太高,則MySQL可能會忽略索引,進行全表掃描,此時使用表鎖。可使用 force index 強制使用索引。
鎖問題
臟讀
臟數(shù)據(jù):指的是事務對緩沖池中行記錄的修改,并且還沒有提交。即事務未提交的數(shù)據(jù)。
臟讀:指當前事務可以讀到其他事務的未提交的數(shù)據(jù)。如果讀到了臟數(shù)據(jù),即一個事務可以讀到另外一個事務中未提交的數(shù)據(jù),顯然違反了事務的隔離性。
臟讀的條件:需要事務的隔離級別為讀未提交。
示例:
不可重復讀
不可重復讀:指在在一個事務內(nèi)多次讀取同一個數(shù)據(jù)集合,在這個事務還沒有結(jié)束時,另外一個事務也訪問了同一個數(shù)據(jù)集合,并且做了一些DML操作。因此,在第一個事務的兩次讀數(shù)據(jù)之間,由于第二個事務的修改,第一個事務兩次讀取到的數(shù)據(jù)可能是不一樣的(具體看隔離級別)。這種稱為不可重復讀。
示例:
丟失更新
丟失更新:指一個事務的更新操作被另外一個事務的更新操作所覆蓋,從而導致數(shù)據(jù)的不一致。
丟失更新的實例:
解決辦法:對用戶讀取的記錄加上一個排他鎖,這樣子其他事務就必須等待前一個事務的完成。從而避免并發(fā)問題。
解決辦法的示例:
阻塞
阻塞:事務因為等待其他事務釋放鎖而等待
超時:等待其他事務釋放鎖,超過超時時間,就認為是超時。
innodb_lock_wait_timeout:用來控制超時時間,默認是50秒。可以在MYSQL運行時進行設置。
innodb_rollback_on_timeout:用來設定是否在等待超時時對進行中的事務進行回滾操作。默認是OFF,不回滾。不可以在MySQL啟動時進行修改。用戶在超時的情況下,必須判斷是是否需要commit或者rollback,之后再進行下一步的操作。
死鎖
概念:死鎖是指兩個或者兩個以上的事務,因爭奪資源而造成的一種互相等待的現(xiàn)象。若無外力作用,所有事務都將無法推進下去。
解決數(shù)據(jù)庫死鎖最簡單的方法:設置超時時間。即當兩個事務互相等待時,當一個等待時間超過設置的閾值時,其中一個事務進行回滾,另外一個等待的事務就能繼續(xù)執(zhí)行。
超時機制雖然簡單,但是其使用FIFO的方式來選擇超時回滾的事務,假如第一個超時的事務 更新了很多行,遠比第二個事務多,因此占用了更多的undo log,這時FIFO的方式,就顯得不適用了,因為第一個事務回滾時間明顯比第二個事務回滾時間長很多。
等待圖
因為FIFO處理死鎖可能不適用,所以數(shù)據(jù)庫普遍采用了wait-for graph(等待圖)的方式來進行死鎖檢測。和超時機制比較,這是一種更為主動的死鎖檢測方式,InnoDB也采用了這種方式。
等待圖要求數(shù)據(jù)庫保存以下兩種信息:
(1)鎖的信息鏈表(見圖6-5)
(2)事務等待鏈表(見圖6-5)
通過上述鏈表可以構(gòu)造出一張圖,而在這個圖中存在回路,則代表存在死鎖。在等待圖中,事務為圖中的節(jié)點。在圖中,事務T1指向事務T2邊的定義為:
(1)事務T1等待事務T2所占用的資源
(2)事務之間在等待相同的資源,而事務T1在事務T2之后
發(fā)現(xiàn)死鎖后,InnoDB會馬上回滾一個事務。
鎖升級
概念:將當前鎖的粒度降低,比如說把行鎖升級為表鎖,那樣子會導致并發(fā)性能降低。
InnoDB不是根據(jù)每個記錄來產(chǎn)生行鎖的,而是根據(jù)每個事務訪問的每個頁對鎖進行管理的,采用的是位圖的方式,因此不管一個事務鎖住頁中一條還是多條記錄,都是用一個鎖,其開銷通常是一致的。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的mysql lock_MySQL-锁总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: db2 sql 判断select是否为空
- 下一篇: Java面向对象基础接口和抽象的理解