這樣一優(yōu)化系統(tǒng)整體性能立馬提升
環(huán)境:Spring5.3.23
1. 案例代碼
先看下示例代碼:
static class PersonService {
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 這里的代碼,先執(zhí)行了更新操作,然后執(zhí)行查詢操作。
* 業(yè)務(wù)代碼非常的簡(jiǎn)單
*/
@Transactional
public void operator() {
// 1
int res = this.jdbcTemplate.update("update p_user t set t.address='akf' where t.id = 9000000") ;
System.out.printf("更新: %d 條數(shù)據(jù)%n", res) ;
// 2
List<Map<String, Object>> users = this.jdbcTemplate.queryForObject("select * from p_user x where x.username='h4F7i4B4'", (rs, rowNum) -> {
List<Map<String, Object>> datas = new ArrayList<>() ;
while (rs.next()) {
Map<String, Object> obj = new HashMap<>() ;
obj.put("id", rs.getObject(1)) ;
obj.put("username", rs.getObject(2)) ;
obj.put("email", rs.getObject(3)) ;
obj.put("password", rs.getObject(4)) ;
obj.put("address", rs.getObject(5)) ;
obj.put("age", rs.getObject(6)) ;
datas.add(obj) ;
}
return datas ;
}) ;
System.out.println(users) ;
}
}
先思考上面代碼在什么樣的場(chǎng)景下可以做相應(yīng)的優(yōu)化?
2. 環(huán)境準(zhǔn)備
本地環(huán)境我準(zhǔn)備了p_user表數(shù)據(jù)量2000W。
圖片
數(shù)據(jù)庫(kù)索引情況:
圖片
這里除了主鍵,沒(méi)有任何其它的索引。
這里不建立任何二級(jí)索引是為了模擬查詢慢的場(chǎng)景。
執(zhí)行上面的查詢SQL:
圖片
執(zhí)行時(shí)間5s;感覺(jué)還好。
3. 代碼分析
在上面的代碼中2個(gè)關(guān)鍵的數(shù)據(jù)庫(kù)操作分別是更新與查詢,這兩個(gè)查詢是沒(méi)有任何關(guān)聯(lián)的,相關(guān)獨(dú)立;而在整個(gè)方法上是添加了@Transaction注解,整個(gè)方法的執(zhí)行都是在一個(gè)事務(wù)中執(zhí)行。那么這里的查詢?nèi)绻浅5穆遣皇菚?huì)對(duì)當(dāng)前的修改記錄或者是整個(gè)p_user表造成影響呢?
結(jié)合上面的數(shù)據(jù)表情況,當(dāng)前表是沒(méi)有任何索引的(除主鍵)。如果這里的查詢比較慢,會(huì)發(fā)生什么情況呢?
首先你要知道MySQL中update語(yǔ)句會(huì)上什么鎖?
回顧MySQL鎖:
MySQL的UPDATE語(yǔ)句默認(rèn)會(huì)帶上一個(gè)寫鎖(Write Lock)。在MySQL中,寫鎖會(huì)阻塞其他事務(wù)對(duì)同一行的讀取和寫入操作,以確保數(shù)據(jù)的一致性和完整性。
當(dāng)執(zhí)行UPDATE語(yǔ)句時(shí),MySQL會(huì)在相關(guān)的行上獲取寫鎖。這樣,其他事務(wù)無(wú)法修改或刪除這些行,直到寫鎖被釋放。這有助于防止在并發(fā)操作中發(fā)生數(shù)據(jù)沖突或不一致的情況。
還有點(diǎn)要注意mysql的鎖機(jī)制是基于事務(wù)的,所以通常我們需要在一個(gè)事務(wù)中進(jìn)行操作。隨著事務(wù)的提交或回滾進(jìn)行鎖的釋放
知道了update語(yǔ)句默認(rèn)會(huì)帶上寫鎖,那么這里的update鎖的是id等于9000000的數(shù)據(jù)。前面提到了,只要事務(wù)提交或者回滾后鎖才會(huì)被釋放。
那如果這里我們接下來(lái)的查詢比較慢那是不是我們這個(gè)鎖的釋放時(shí)間就會(huì)變長(zhǎng),其它事務(wù)將會(huì)被阻塞。一旦發(fā)生了阻塞我們系統(tǒng)的整體性能可能會(huì)受到影響。這里的update語(yǔ)句只會(huì)對(duì)id為9000000的數(shù)據(jù)上鎖,如果咱們的更新語(yǔ)句是范圍的或者條件是沒(méi)有索引的那很可能就成了表鎖,那這時(shí)候系統(tǒng)的性能會(huì)變的非常糟糕。也就是我們對(duì)這條id為9000000的數(shù)據(jù)要鎖至少5s時(shí)間。
該如何優(yōu)化上面的代碼呢?
4. 代碼優(yōu)化
通過(guò)上面的分析,由于先執(zhí)行了update語(yǔ)句,然后執(zhí)行查詢語(yǔ)句,如果查詢比較慢那么我們的update語(yǔ)句形成的寫鎖(行鎖,間隙鎖或者表鎖)時(shí)間會(huì)變長(zhǎng),對(duì)系統(tǒng)的整體性能會(huì)造成影響。所以這里我們只需要將查詢和修改操作順序進(jìn)行下調(diào)整即可。
@Transactional
public void operator() {
// 1
List<Map<String, Object>> users = this.jdbcTemplate.queryForObject("select * from p_user x where x.username='h4F7i4B4'", (rs, rowNum) -> {
List<Map<String, Object>> datas = new ArrayList<>() ;
while (rs.next()) {
Map<String, Object> obj = new HashMap<>() ;
obj.put("id", rs.getObject(1)) ;
// ...
datas.add(obj) ;
}
return datas ;
}) ;
System.out.println(users) ;
// 2
int res = this.jdbcTemplate.update("update p_user t set t.address='akf' where t.id = 9000000") ;
System.out.printf("更新: %d 條數(shù)據(jù)%n", res) ;
}
通過(guò)上面的優(yōu)化,雖然該接口自身的性能并沒(méi)有提升,但是在該接口中update語(yǔ)句形成的鎖時(shí)間將大大的減少(在這里查詢語(yǔ)句是沒(méi)有鎖的),如果同一時(shí)刻存在其它事務(wù)修改當(dāng)前的數(shù)據(jù)不至于被阻塞太長(zhǎng)時(shí)間,那其它接口的性能整體不就提高了么。
在上面的代碼中首先是2個(gè)操作互不相干,其實(shí)完全可以把不需要事務(wù)的操作放到其它方法中(注意事務(wù)失效問(wèn)題)。
static class PersonService {
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private PersonService ps ;
public void query() {
ps.update() ;
List<Map<String, Object>> users = this.jdbcTemplate.queryForObject("select * from p_user x where x.username='h4F7i4B4'", (rs, rowNum) -> {
List<Map<String, Object>> datas = new ArrayList<>() ;
while (rs.next()) {
Map<String, Object> obj = new HashMap<>() ;
obj.put("id", rs.getObject(1)) ;
// ...
datas.add(obj) ;
}
return datas ;
}) ;
System.out.println(users) ;
}
@Transactional
public void update() {
int res = this.jdbcTemplate.update("update p_user t set t.address='akf' where t.id = 9000000") ;
System.out.printf("更新: %d 條數(shù)據(jù)%n", res) ;
}
}
上面代碼己注入自己,解決在非事務(wù)方法中調(diào)用事務(wù)方法二導(dǎo)致事務(wù)失效。當(dāng)然也可以通過(guò)AopContext來(lái)解決(不推薦),也可以把事務(wù)方法放到其它類中都可以解決。
完畢!!!