本篇文章主要介紹的是 MySQL / JDBC 中的事務(wù)瑞你,為了方便讀者瀏覽酪惭,這里默認(rèn)需要讀者已經(jīng)掌握 SQL基礎(chǔ) 以及 JDBC 數(shù)據(jù)庫連接基礎(chǔ)。這部分的基礎(chǔ)也可以參考下面的鏈接進(jìn)行簡單的快速入門者甲。
1.概述
MySQL 事務(wù)主要用于處理操作量大春感,復(fù)雜度高的數(shù)據(jù)。比如說虏缸,在銀行轉(zhuǎn)賬系統(tǒng)中鲫懒,A -> B 轉(zhuǎn)賬 1000 元,這時(shí)就需要將 A 賬戶余額 -1000刽辙,對應(yīng)的 B 賬戶余額 +1000窥岩。這兩個(gè)操作過程必須同時(shí)執(zhí)行成功才能完成此操作,這樣扫倡,這些數(shù)據(jù)庫操作語句就構(gòu)成了一個(gè)事務(wù)谦秧。
- 在上面的舉例中如果 A 的賬戶余額 -1000 執(zhí)行完畢后竟纳,程序被中斷了(拋出異常、服務(wù)器宕機(jī)等)疚鲤,而 B 賬戶沒有 +1000 元锥累,這肯定是有問題的。
- 現(xiàn)在對事務(wù)應(yīng)該有一個(gè)了解了吧??事務(wù)中的多個(gè)操作集歇,或者全部執(zhí)行完畢桶略,或者全不執(zhí)行淘讥,不存在只執(zhí)行了一部分的情況掏婶。
2.事務(wù)的四大特性(ACID)
- 原子性(Atomicity):事務(wù)中的所有操作是不可再分割的原子單位贯卦,事務(wù)中的所有操作是一個(gè)整體内狗,或者整體執(zhí)行成功耍共,亦或者整體執(zhí)行失敗砾隅。
- 一致性(Consistency):事務(wù)執(zhí)行后储狭,數(shù)據(jù)庫狀態(tài)與其他業(yè)務(wù)規(guī)則保持一致熔掺。如轉(zhuǎn)賬業(yè)務(wù)纺荧,無論執(zhí)行成功與否旭愧,參與轉(zhuǎn)賬的兩個(gè)帳號余額值和應(yīng)該是不變的。
- 隔離性(Isolation):在并發(fā)操作中宙暇,不同事務(wù)之間應(yīng)該隔離開來输枯,每個(gè)并發(fā)中的事務(wù)的執(zhí)行不會相互干擾。
- 持久性(Durability):一旦事務(wù)提交成功占贫,事務(wù)中的所有數(shù)據(jù)更新必須被持久化到數(shù)據(jù)庫中桃熄,即使提交事務(wù)后,數(shù)據(jù)庫馬上崩潰型奥,在數(shù)據(jù)庫重新啟動時(shí)瞳收,也必須能保證通過某種機(jī)制恢復(fù)數(shù)據(jù)。
3.MySQL 中的事務(wù)
在默認(rèn)情況下桩引,MySQL 每執(zhí)行一條 SQL 語句缎讼,都是一個(gè)單獨(dú)的事務(wù)。如果需要在每一個(gè)事務(wù)中包含多條 SQL 語句的執(zhí)行坑匠,那么就需要開啟事務(wù)和結(jié)束事務(wù)血崭。
- 開啟事務(wù):
START TRANSACTION
- 結(jié)束事務(wù):
COMMIT
或ROLLBACK
- 在執(zhí)行 SQL 語句之前,先執(zhí)行
START TRANSACTION
厘灼,則代表開啟了一個(gè)事務(wù)夹纫,然后執(zhí)行多條 SQL 語句,最后需要結(jié)束事務(wù)设凹,COMMIT
表示提交舰讹,即事務(wù)中的多條 SQL 語句所更改的數(shù)據(jù)會持久化到數(shù)據(jù)庫中∩林欤或者ROLLBACK
表示回滾月匣,即回滾到事務(wù)的起點(diǎn)钻洒,將之前所做的所有操作撤銷。
Reiminder ???♂?
ROLLBACK
可以結(jié)束事務(wù)锄开,但不代表會將數(shù)據(jù)持久化到數(shù)據(jù)庫中素标,而只有COMMIT
提交才可以將數(shù)據(jù)持久化到數(shù)據(jù)庫中。
- 測試表:
# 創(chuàng)建 Account 表
CREATE TABLE `Account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`balance` decimal(10,0) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `Account_id_uindex` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
# 插入數(shù)據(jù)
INSERT INTO Account (name, balance) VALUES ('A', 10000);
INSERT INTO Account (name, balance) VALUES ('B', 10000);
INSERT INTO Account (name, balance) VALUES ('C', 10000);
3.1 - COMMIT 測試
# 開啟事務(wù)
START TRANSACTION;
# 執(zhí)行事務(wù) SQL 語句
# SQL1
UPDATE Account
SET balance = balance - 1000
WHERE id = 1;
# SQL2
UPDATE Account
SET balance = balance + 1000
WHERE id = 2;
# 提交事務(wù)
COMMIT;
# 結(jié)果分析
1 A 9000
2 B 11000
3 C 10000
分析:提交事務(wù)后萍悴,更新的數(shù)據(jù)將被持久化到數(shù)據(jù)庫中头遭。
3.2 - ROOLBACK 測試
# 開啟事務(wù)
START TRANSACTION;
# 執(zhí)行事務(wù) SQL 語句
# SQL1
UPDATE Account
SET balance = balance - 1000
WHERE id = 1;
# SQL2
UPDATE Account
SET balance = balance + 1000
WHERE id = 2;
# 回滾事務(wù)
ROLLBACK;
# 提交事務(wù)
COMMIT;
# 結(jié)果分析
1 A 10000
2 B 10000
3 C 10000
分析:事務(wù)提交前執(zhí)行 ROLLBACK 回滾事務(wù)至 START TRANSACTION 時(shí)的狀態(tài),所以持久化后數(shù)據(jù)庫中數(shù)據(jù)沒有被改變癣诱。
3.3 - 事務(wù)不提交測試
# 開啟事務(wù)
START TRANSACTION;
# 執(zhí)行事務(wù) SQL 語句
# SQL1
UPDATE Account
SET balance = balance - 1000
WHERE id = 1;
# SQL2
UPDATE Account
SET balance = balance + 1000
WHERE id = 2;
# 輸出結(jié)果
SELECT *
FROM Account;
# 控制臺打印數(shù)據(jù)
1 A 9000
2 B 11000
3 C 10000
# 結(jié)果分析(數(shù)據(jù)庫數(shù)據(jù))
1 A 10000
2 B 10000
3 C 10000
分析:在執(zhí)行了 SQL 語句后计维,在內(nèi)存中的數(shù)據(jù)表數(shù)據(jù)已經(jīng)被修改了,但是由于沒有提交事務(wù)撕予,所以數(shù)據(jù)沒有被持久化到數(shù)據(jù)庫中鲫惶。
4.并發(fā)事務(wù)問題
- 臟讀(Dirty Read):在事務(wù)的執(zhí)行過程中,讀取到了其他事務(wù)的 未提交 的數(shù)據(jù)实抡,即讀到了臟數(shù)據(jù)剑按。
- 不可重復(fù)讀(Unrepeatable Read):在事務(wù)的執(zhí)行過程中,讀到了其他事務(wù) 修改后 的數(shù)據(jù)澜术,換句話說在該事務(wù)中的不同時(shí)間點(diǎn)讀取到了不一致的數(shù)據(jù),即不可重復(fù)讀猬腰。
- 幻讀/虛讀(Phantom Read):在事務(wù)的執(zhí)行過程中鸟废,讀取到了其他事務(wù)對 記錄數(shù) 修改后的數(shù)據(jù),對同一張表的兩次查詢的
COUNT(*)
不一致姑荷。 - 不可重復(fù)讀與幻讀的區(qū)別:
- 不可重復(fù)讀:強(qiáng)調(diào)的是數(shù)據(jù) 內(nèi)容 的不一致盒延,主要針對
UPDATE
的修改。 - 幻讀:強(qiáng)調(diào)的是 記錄數(shù) 的不一致鼠冕,主要針對
INSERT
/DELETE
的修改添寺。
- 不可重復(fù)讀:強(qiáng)調(diào)的是數(shù)據(jù) 內(nèi)容 的不一致盒延,主要針對
5.四大隔離級別
剛剛我們介紹了事務(wù)并發(fā)時(shí)可能出現(xiàn)的各種問題,其實(shí)可以發(fā)現(xiàn)是違背了事務(wù)的 隔離性 的要求所引起的懈费,所以我們需要通過事務(wù)的隔離來解決這個(gè)問題计露,下面我們就來介紹一下事務(wù)的四大隔離級別。
- 串行化(SERIALIZABLE):
- 概述:對數(shù)據(jù)串行的訪問憎乙,非并發(fā)訪問票罐。
- 特點(diǎn):不會出現(xiàn)任何并發(fā)問題,性能最差泞边。
- 理解:在當(dāng)前串行化事務(wù)中该押,如果有其他事務(wù)對數(shù)據(jù)進(jìn)行了增刪改操作,當(dāng)前事務(wù)讀取數(shù)據(jù)會被阻塞阵谚,需要等到其他事務(wù)結(jié)束后(ROLLBACK/COMMIT)才能執(zhí)行數(shù)據(jù)讀取蚕礼。
- 可重復(fù)讀(REPEATABLE READ):
- 概述:在一個(gè)事務(wù)的執(zhí)行過程中烟具,能保證讀取到數(shù)據(jù)的一致性,是 MySQL 中使用的 InnoDB 存儲引擎默認(rèn)的隔離級別奠蹬。
- 特點(diǎn):可避免臟讀和不可重復(fù)讀朝聋,不能避免幻讀問題,并發(fā)式讀取訪問罩润,性能比串行化好玖翅。
- 理解:在一個(gè)事務(wù)內(nèi),鎖定讀取割以,通過保存第一次讀取的快照(snapshot)金度,保證每次讀取的數(shù)據(jù)一致。
- 讀已提交(READ COMMITTED):
- 概述:在一個(gè)事務(wù)內(nèi)严沥,可以讀取到其他事務(wù)已經(jīng)提交的數(shù)據(jù)猜极,性能優(yōu)于可重復(fù)讀(REPEATABLE READ),Oracle 數(shù)據(jù)庫中的默認(rèn)隔離級別消玄。
- 特點(diǎn):可避免臟讀跟伏,不能避免不可重復(fù)讀和幻讀問題,并發(fā)式訪問讀取翩瓜,性能比可重復(fù)讀好受扳。
- 理解:MySQL中與 Oracle 的該隔離級別,通過讀取新鮮的快照(fresh snapshot)來讀取其他事務(wù)已提交的更新內(nèi)容兔跌。
- 讀未提交(READ UNCOMMITTED):
- 概述:在一個(gè)事務(wù)內(nèi)勘高,可以讀取到其他事務(wù)沒有提交的修改內(nèi)容,即臟讀(Dirty Read)坟桅。
- 特點(diǎn):不能避免任何并發(fā)事務(wù)的問題华望,性能最好。
- 理解:在 SERIALIZBLE 的事務(wù)隔離級別仅乓,InnoDB 存儲引擎會對每個(gè) SELECT 語句后自動加上 LOCK IN SHARE MODE赖舟,即給每個(gè)讀取操作加一個(gè)共享鎖,因此在這個(gè)事務(wù)隔離級別下夸楣,讀占用鎖了宾抓,一致性的非鎖定讀不再予以支持,一般不會在本地事務(wù)中使用 SERIALIZBLE 的隔離級別豫喧,SERIALIZABLE 的事務(wù)隔離級別主要用于 InnoDB 存儲引擎的分布式事務(wù)洞慎。
隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
串行化(SERIALIZABLE) | ?? | ?? | ?? |
可重復(fù)讀(REPEATABLE READ) | ?? | ?? | - |
讀已提交(READ COMMITTED) | ?? | ? | ? |
讀未提交(READ UNCOMMITTED) | ? | ? | ? |
6.MySQL 各隔離級別的并發(fā)事務(wù)測試
查看隔離級別:MySQL 默認(rèn)隔離級別是
REPEATABLE-READ
,可以通過SELECT @@TX_ISOLATION;
查看隔離級別嘿棘。設(shè)置隔離級別:
SET SESSION TRANSACTION ISOLATION LEVEL xxx;
測試表及數(shù)據(jù)
id | name | balance |
---|---|---|
1 | A | 10000 |
2 | B | 10000 |
6.1 - 串行化測試
- 測試版本:
MySQL Server 5.7
- 測試環(huán)境:
# 設(shè)置窗口 2 隔離級別為 串行化
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
# 特別的 -> 窗口 1 的隔離級別不需要特別設(shè)置劲腿。
# 我們演示是通過窗口 1 進(jìn)行修改數(shù)據(jù)值,在窗口 2 來觀察結(jié)果的鸟妙。
值得注意??在第 3 步焦人,窗口 1 執(zhí)行 INSERT 插入了一條數(shù)據(jù)挥吵,而后第 4 步窗口 2 執(zhí)行 SELECT 操作會被阻塞(避免幻讀),直到窗口 1 事務(wù)結(jié)束(COLLBACK/COMMIT)后才會被執(zhí)行花椭。
特別的??當(dāng)窗口 2 一旦執(zhí)行過 SELECT 操作后忽匈,如果有其他事務(wù)對數(shù)據(jù)進(jìn)行增刪改操作都將被阻塞(可重復(fù)讀的保證),直到該串行化事務(wù)結(jié)束后才會被執(zhí)行矿辽。
6.2 - 可重復(fù)讀測試
與串行化類似的是丹允,當(dāng)窗口 2 執(zhí)行步驟 3 讀操作后,查詢的結(jié)果將被鎖定袋倔。當(dāng)其他事務(wù)要對該鎖定數(shù)據(jù)執(zhí)行更改操作時(shí)都將會被阻塞雕蔽,所以當(dāng)窗口 1 執(zhí)行步驟 4 時(shí)將會被阻塞,從而保證了可重復(fù)讀宾娜。
6.3 - 讀已提交測試
當(dāng)步驟 4 修改了
balance
值時(shí)批狐,此時(shí)還未提交,所以步驟 5 查詢到的結(jié)果并沒有改變(讀已提交)前塔,而在步驟 7 查詢到了窗口 1 改變的結(jié)果嚣艇,因?yàn)榇藭r(shí)窗口 1 的事務(wù)已經(jīng)提交。
特別的??在窗口 2 事務(wù)的執(zhí)行過程中华弓,步驟 3 與步驟 7 查詢到了不同的結(jié)果食零,由此可以看出這是與可重復(fù)讀的重要區(qū)別。
6.4 - 讀未提交測試
步驟 4 中窗口 1 事務(wù)修改了數(shù)據(jù)寂屏,步驟 5 中窗口 2 事務(wù)讀取到了修改后的數(shù)據(jù)慌洪,此時(shí)窗口 1 事務(wù)還未提交,因此讀取到的是 臟數(shù)據(jù)凑保,該隔離級別不能避免任何的并發(fā)事務(wù)問題。
7.JDBC 事務(wù)
剛剛我們介紹了在 MySQL 中對事務(wù)進(jìn)行的操作涌攻,而 JDBC 中 也必然有與對應(yīng)的方式進(jìn)行事務(wù)控制欧引,下面我們介紹一下 JDBC 中對事務(wù)的控制。
- 在 JDBC 中處理事務(wù)都是通過 Connection 完成的恳谎。
- 同一個(gè)事務(wù)中的所有的操作芝此,都是使用同一個(gè) Connection 對象。
7.1 - 開啟事務(wù)
- 方法:
void setAutoCommit(boolean autoCommit)
讀讀 API ??
If a connection is in auto-commit mode, then all its SQL statements will be executed and committed as individual transactions. Otherwise, its SQL statements are grouped into transactions that are terminated by a call to either the method commit or the method rollback. By default, new connections are in auto-commit mode.
- 如果 connection 處于自動提交模式因痛,會將每一條 SQL 語句作為一個(gè)單獨(dú)的事務(wù)提交(commit)婚苹;否則,其 SQL 語句可以通過調(diào)用
commit()
方法或rollback()
方法終止事務(wù)鸵膏。默認(rèn)是自動提交模式膊升。
Reminder ???♂?
Java 還特別指出:對于 DML 語句,例如插入谭企、 更新或刪除和 DDL 語句廓译,該語句是完整的盡快它執(zhí)行完评肆。
Select 語句,該語句完成時(shí)關(guān)閉關(guān)聯(lián)的 ResultSet非区。
7.2 - 提交事務(wù)
- 方法:
commit()
讀讀 API ??
Makes all changes made since the previous commit/rollback permanent and releases any database locks currently held by this Connection object. This method should be used only when auto-commit mode has been disabled.
- 提交自上次提交后的所有更改瓜挽,并釋放目前此連接對象的任何 數(shù)據(jù)庫鎖。
- 只有當(dāng)禁用了自動提交時(shí)此方法有效征绸。
7.3 - 回滾事務(wù)
- 方法:
rollback()
讀讀 API ??
Undoes all changes made in the current transaction and releases any database locks currently held by this Connection object. This method should be used only when auto-commit mode has been disabled.
- 撤銷對當(dāng)前事務(wù)中所做的所有更改久橙,并釋放目前此連接對象持有的任何 數(shù)據(jù)庫鎖。
- 只有當(dāng)禁用了自動提交時(shí)此方法有效管怠。
7.4 - 設(shè)置保存點(diǎn)
- 方法:
Savepoint setSavepoint(String name)
讀讀 API ??
Creates a savepoint with the given name in the current transaction and returns the new Savepoint object that represents it.
if setSavepoint is invoked outside of an active transaction, a transaction will be started at this newly created savepoint.
- 在當(dāng)前事務(wù)中創(chuàng)建一個(gè)指定名稱的保存點(diǎn)淆衷,并返回一個(gè)用來表示它的新的保存點(diǎn)對象。
- 如果該方法在一個(gè)事務(wù)外被調(diào)用時(shí)排惨,將在這個(gè)新創(chuàng)建的保存點(diǎn)啟動事務(wù)吭敢。
7.5 - 事務(wù)回滾
- 不帶保存點(diǎn)的 JDBC 事務(wù)的基本格式:
try {
connection.setAutoCommit(false); // 禁用自動提交
...
...
connection.commit(); // 在 try 的末尾提交
} catch() {
connection.rollback(); // 事務(wù)執(zhí)行中斷則回滾
}
- 代碼示例:
public static void transfer(boolean b) throws Throwable {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JdbcUtils.getConnection();
// 禁用自動提交
connection.setAutoCommit(false);
String sql = "UPDATE Account SET balance = balance + ? WHERE id = ?";
preparedStatement = connection.prepareStatement(sql);
// 操作 1
preparedStatement.setDouble(1, -10000);
preparedStatement.setInt(2, 1);
preparedStatement.executeUpdate();
// 在事務(wù)的兩個(gè)操作中拋出異常,中斷事務(wù)內(nèi)務(wù)的執(zhí)行
if (b) {
throw new Exception();
}
// 操作 2
preparedStatement.setDouble(1, 10000);
preparedStatement.setInt(2, 2);
preparedStatement.executeUpdate();
// 提交事務(wù)
connection.commit();
} catch (Exception e) {
try {
if (connection != null) {
connection.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
throw new RuntimeException();
} finally {
JdbcUtils.release(connection, preparedStatement);
}
}
7.6 - 回滾到保存點(diǎn)
- 概述:保存點(diǎn)(savePoint) 是 JDBC 3.0 的 API暮芭,其要求數(shù)據(jù)庫支持以保存點(diǎn)方式的的回滾鹿驼。
- 檢查方法:
boolean b = connection.getMetaData().supportsSavepoints();
- 回滾到保存點(diǎn)方法:
void rollback(Savepoint savepoint)
- 作用:保存點(diǎn)的作用是將事務(wù)回滾到指定的保存點(diǎn)。需要在事務(wù)中先設(shè)置好保存點(diǎn)辕宏,然后回滾時(shí)通過
Savepoint
回滾到指定的保存點(diǎn)畜晰,而不是回滾整個(gè)事務(wù)。
Reminder ???♂?
回滾到指定的保存點(diǎn)并沒有結(jié)束事務(wù)瑞筐,只有回滾了整個(gè)事務(wù)才會結(jié)束事務(wù)凄鼻。
- 代碼示例:
/*
* 李四對張三說,如果你給我轉(zhuǎn)1W聚假,我就給你轉(zhuǎn)100W块蚌。
* ==========================================
*
* 張三給李四轉(zhuǎn)1W(張三減去1W,李四加上1W)
* 設(shè)置保存點(diǎn)膘格!
* 李四給張三轉(zhuǎn)100W(李四減去100W峭范,張三加上100W)
* 查看李四余額為負(fù)數(shù),那么回滾到保存點(diǎn)瘪贱。
* 提交事務(wù)
*/
private static void savepoint() throws RuntimeException {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JdbcUtils.getConnection();
// 禁用自動提交
connection.setAutoCommit(false);
String sql = "UPDATE Account SET balance = balance + ? WHERE name = ?";
preparedStatement = connection.prepareStatement(sql);
// 操作1(張三減去1W)
preparedStatement.setDouble(1, -10000);
preparedStatement.setString(2, "zs");
preparedStatement.executeUpdate();
// 操作2(李四加上1W)
preparedStatement.setDouble(1, 10000);
preparedStatement.setString(2, "ls");
preparedStatement.executeUpdate();
// 設(shè)置表存點(diǎn)
Savepoint savepoint = connection.setSavepoint();
// 操作3(李四減去100W)
preparedStatement.setDouble(1, -1000000);
preparedStatement.setString(2, "ls");
preparedStatement.executeUpdate();
// 操作4(張三加上100W)
preparedStatement.setDouble(1, 1000000);
preparedStatement.setString(2, "zs");
preparedStatement.executeUpdate();
// 操作5(查看李四余額)
sql = "SELECT balance FROM Account WHERE name = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "ls");
ResultSet resultSet = preparedStatement.executeQuery();
double balance = 0;
if (resultSet.next()) {
balance = resultSet.getDouble("balance");
}
// 如果李四的余額為負(fù)數(shù)纱控,那么回滾到指定保存點(diǎn)
if (balance < 0) {
connection.rollback(savepoint);
System.out.println("張三你上當(dāng)了");
}
// 提交事務(wù)
connection.commit();
} catch (SQLException e) {
// 回滾事務(wù)
if (connection != null) {
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
throw new RuntimeException();
} finally {
JdbcUtils.release(connection, preparedStatement);
}
}
悄悄話 ??
- 年后的學(xué)習(xí)節(jié)奏變得非常之快,導(dǎo)致最近也很久沒有與大家分享技術(shù)筆記了菜秦,最近學(xué)習(xí)了 JavaWeb 的 HTML甜害、CSS、JS球昨、MySQL尔店、Tomcat、Servlet、JSP 等等內(nèi)容闹获,哪一個(gè)技術(shù)拿出來都應(yīng)該可以讓我來研究一陣子了期犬,怎奈進(jìn)度太快也只能是抓大放小。最近在數(shù)據(jù)庫階段的事務(wù)控制的部分我比較感興趣并做了一些小實(shí)驗(yàn)避诽,覺得有一些意義龟虎,所以來與大家分享一下。
- 后面的 Cookie沙庐、Session 技術(shù)也是我覺得理解的比較深入的一個(gè)技術(shù)點(diǎn)鲤妥,我會留在下次的更新中進(jìn)行分享。
彩蛋 ??
-
最近在整理一些 JavaWeb 成長之路 的一些學(xué)習(xí)筆記拱雏,本篇是 Database 系列中的一篇棉安,今后還會與大家分享 JavaWeb 中的一系列的技術(shù),有興趣的朋友可以關(guān)注我的專題铸抑,一同學(xué)習(xí)贡耽。
如果你覺得我的分享對你有幫助的話,請?jiān)谙旅??隨手點(diǎn)個(gè)喜歡 ??鹊汛,你的肯定才是我最大的動力蒲赂,感謝。