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