ShardingJdbc 怎麼處理寫完數據立即讀的情況的呢?
寫在前面
我本地使用了兩個庫來做寫庫(ds_0_master)和讀庫(ds_0_salve),兩個庫並沒有配置主從。
下面我就使用庫裏的 city 表做實驗。主庫的 city 表沒有數據,而從庫的 city 表就一條數據
我們討論 4 種情況:
- 常規寫完讀
- 在一個 service 裏面調用另一個 service2 進行讀
- 在一個 service 裏面新開一個線程去調用 service2
- 在一個 service 裏面調用 service2,但 service2 是新開的事務
先直接上實驗結果:
1. 常規寫完讀
@Service
public class CityService {
@Autowired
private CityRepository cityRepository;
@Autowired
private CityService2 cityService2;
@Transactional(rollbackFor = Exception.class)
public void test(){
City city=new City();
city.setName("眉山");
city.setProvince("四川");
cityRepository.save(city);
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主庫":"從庫")+":"+x);
});
}
}
打印結果:
實驗分析:
我們對 city 表進行插入後,緊接着對 city 表進行了查詢,查出的內容是我們剛剛插入的內容。說明查詢操作沒有走讀庫,而是走了主庫。
2. 在一個 service 裏面調用另一個 service
代碼如下:
@Transactional(rollbackFor = Exception.class)
public void test(){
City city=new City();
city.setName("眉山");
city.setProvince("四川");
cityRepository.save(city);
//調用其他service
cityService2.test();
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主庫":"從庫")+":"+x);
});
}
}
service2 的代碼:
public void test(){
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主庫":"從庫")+":"+x);
});
}
打印結果:
實驗分析:在 service 方法裏調用了其他 service,其他 service 也會受到影響。service2 也是走的主庫。
3. 新開一個線程去調用 service2
代碼如下:
@Service
public class CityService {
@Autowired
private CityRepository cityRepository;
@Autowired
private CityService2 cityService2;
@Transactional(rollbackFor = Exception.class)
public void test(){
City city=new City();
city.setName("眉山");
city.setProvince("四川");
cityRepository.save(city);
new Thread(()->{cityService2.test();}).start();
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主庫":"從庫")+":"+x);
});
}
}
@Service
public class CityService2 {
@Autowired
private CityRepository cityRepository;
public void test(){
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主庫":"從庫")+":"+x);
});
}
}
打印結果:
實驗分析:
我們新開了線程對 city 表進行查詢,此次查詢讀的是從庫。新開的線程會走從庫,我猜想是新開的線程它認爲是沒有寫入/修改操作,所以走了從庫。
我又改動了 service2,加了一段寫入操作。代碼如下:
public void test(){
City city=new City();
city.setName("成都");
city.setProvince("四川");
cityRepository.save(city);
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主庫":"從庫")+":"+x);
});
}
再次執行,結果如下:
和預想的不一樣,依舊是走的從庫。
4. service2 新開一個事務執行
我們調整 service2 的事務傳播行爲級別。代碼如下:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test(){
List<City> all = cityRepository.findAll();
all.forEach(x->{
System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主庫":"從庫")+":"+x);
});
}
REQUIRES_NEW 的含義是:
強制自己開啓一個新的事務,如果一個事務已經存在,那麼將這個事務掛起.如 ServiceA.methodA()調用 ServiceB.methodB(),methodB()上的傳播級別是 PROPAGATION_REQUIRES_NEW 的話,那麼如果 methodA 報錯,不影響 methodB 的事務,如果 methodB 報錯,那麼 methodA 是可以選擇是回滾或者提交的,就看你是否將 methodB 報的錯誤拋出還是 try catch 了.
打印結果:
實驗分析:
這個結果確實是沒想到,service2 新開了個事務走的是主庫,而 service 裏面的同一個事務裏的寫後讀,反而走了從庫。
實驗總結:
場景 | service | service2 |
---|---|---|
同一個 service 裏寫完讀 | 主庫 | 主庫 |
service 裏寫完調用另一個 servcie 進行讀操作 | 主庫 | 主庫 |
service 裏寫完新開線程調用另一個 servcie 進行讀操作 | 主庫 | 從庫 |
service 裏寫完新開一個事務調用另一個 servcie 進行讀操作 | 從庫 | 主庫 |
常規的寫完讀操作和寫完在另一個 service 裏進行讀操作,都能夠走到主庫,保證了常規業務的正確性,也滿足了我們一般的使用場景了。而新開線程進行讀操作的情況其實比較少,如果非要使用,我們可以用強制指定主庫的方式進行處理。
最後一種情況,service中調用另一個service2(新開事務),原本 service 裏同一個事務的寫完讀操作走到了從庫,一不注意容易引起實際業務bug,需要使用者謹慎使用。大家覺得這是不是ShardingJdbc的一個BUG呢?