Jdk1.8;shardingjdbc 2.0.0;MySQL5.7(应用中配置了2个数据源db1,db2);应用使用表字段shop分库;
本文是根据shardingjdbc2.0.0进行解析的,当时shardingjdbc还没有进入Apache,现在进入了Apache孵化,最新的4.0.0的估计很快就会发布,不管从源码还是项目结构上比较都发生很大的变化,但是分库核心思想都是一样的.
此处只解析简单的带查询条件的select语句.
Ex: select column1,column2,… from table where clumn3=? and clumn4=? …;
@Query(value = "from ShopItemProtectPricePo po")
@Conditions({
@Condition("po.productSaleId = :productSaleId")
})
ShopItemProtectPricePo getShopItemProtectPrice(@Parameter("shop") Long shop, @Parameter("productSaleId") Long productSaleId);
此DAO方法最终生成的查询SQL为:
select id , … , shop , starttime from shop_item_protect_price where productsaleid=?
注意:此处where条件后面只有productsaleid而没有shop,所以此处shardingjdbc 将不能执行按shop分库,shardingjdbc 将会在所有分库上都会执行一次这个查询SQL然后归并结果.
@Query(value = "from ShopItemProtectPricePo po")
@Conditions({
@Condition("po.shop = :shop"),
@Condition("po.productSaleId = :productSaleId")
})
ShopItemProtectPricePo getShopItemProtectPrice(@Parameter("shop") Long shop, @Parameter("productSaleId") Long productSaleId);
SQL where条件带上了shop字段此处就只会产生一个TableUnits并且后面也只会生产一个PreparedStatementUnit,且也只会在一个分库上执行一次,最后还是归并结果.这就是完美无误的根据shop分库.
@Save
void save(ShopItemProtectPricePo shopItemProtectPricePo);
io.shardingjdbc.core.parsing.parser.clause.InsertValuesClauseParser#parseValues
此方法shardingjdbc会判断插入的字段中是否包含有分库规则shardingRule中定义的分库字段shop,我们看insertStatemnet中conditions的值中包含有shop这个条件,很明显我们的PO中是有shop这个字段的,满足分库.
io.shardingjdbc.core.routing.type.simple.SimpleRoutingEngine#generateRoutingResult
当然最后也就只会生成一个tableUnits;
io.shardingjdbc.core.routing.router.ParsingSQLRouter#route(java.lang.String, java.util.List
在此route()中我们可以看到shardingjdbc已经按照我们的设置的id生成策略给我们生成好了id即generateKeys中的值,此处我们也可以看到只有一个执行单元executionUnits.后续就只会在一个分库db1里面执行这个插入操作.
接下来我们把PO中的shop字段删除试试:
此时我们就看到分库需要的条件集合是空的:
也就会根据分库数量生成2个tableUnits:
也就会生成2个执行单元:
执行完之后两个分库中将各自生成一条记录,且这两条记录是一样的,大家也可以自己验证一下.
@Update
void update(ShopItemProtectPricePo shopItemProtectPricePo);
@SaveOrUpdate
void save(ShopItemProtectPricePo shopItemProtectPricePo);
这两个注解在po中主键id不为空时生成的SQL是一样的,会生成以下SQL:
update shop_item_protect_price set basesellprice=?, correct=?, createtime=?, endtime=?, errormsg=?, lastprocesstime=?, onebiteprice=?, outeritemid=?, productsaleid=?, protectprice=?, sellprice=?, starttime=? where id=?
Shardingjdbc会像分析select语句一样分析where后面的条件是否包含有分库字段shop,有的话就只生成对应分库执行单元,并且也就执行在一个分库上,此处生产的where条件只有id不包含shop,故会与上述分析的一样生成2个执行单元.
这是归并前的执行结果,可以and两个分库均执行成功了,且都返回影响行数是1,故总共修改了2条数据(这儿之所有修改了两条数据是因为我人为的在两个分库中创建了两条id一样的数据,正常生产情况下我们的id生成策略是唯一的,所以全局不会出现重复的id,也就是说再执行以下SQL结果集也是两个但是一个影响行数是1其它都是0,此时事务可以正常提交)
io.shardingjdbc.core.executor.type.prepared.PreparedStatementExecutor#executeUpdate
此处shardingjdbc将归并结果,当然1+1=2,此时就该轮到提交事务了,提交事务是报了以下错误,意思就是期望修改1条数据,实际上是修改了2条数据,事务提交失败,两个分库的数据均回滚.
org.springframework.orm.hibernate4.HibernateSystemException: Batch update returned unexpected row count from update [0]; actual row count: 2; expected: 1; nested exception is org.hibernate.jdbc.BatchedTooManyRowsAffectedException: Batch update returned unexpected row count from update [0]; actual row count: 2; expected: 1
没有包含分库字段的SQL会执行在所有分库,会占用大量的连接,严重影响正常业务数据流转.
1.检查应用中是否存在使用Hibernate注解@Update @SaveOrUpdate,存在请使用自定义SQL替换且SQL中where条件必须包含分库字段(此文是shop);
2.检查应用在同一Hibernate session范围内是否存在修改PO的内容,如果存在可以将被hibernate管理的那个PO对象的值拷贝到一个新new 的PO对象,然后使用新对象去做修改.
3.在应用中主键id生成策略是全局唯一的,所以直接使用@Update 和 @SaveOrUpdate注解更新数据,数据完整性是一致的,不会存在乱更新数据,但是存在浪费数据库连接资源.