1、JPA实体saveAll()的问题
下面中会在save的list加入一条会插入失败的数据
public void testAdd() {
List<UserContactPerson> userContactPersonList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
UserContactPerson userContactPerson = new UserContactPerson();
userContactPerson.setId(Long.valueOf(i));
userContactPerson.setUserId(UUID.randomUUID());
userContactPerson.setContactUserId(UUID.randomUUID());
// 模拟异常
if (i == 8) {
// userId在表中为非空,如果为空会插入失败
userContactPerson.setUserId(null);
}
userContactPersonList.add(userContactPerson);
}
userContactRepository.saveAll(userContactPersonList);
}
插入失败会下面的异常,会导致其他正确的数据也无法插入
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'user_id' cannot be null
...
...
...
建议使用saveAll()时,应该确保对保存每一个数据不会出现失败,不然会导致其他数据无法保存。或者在发生错误失败时,应该有相应的捕获异常机制,将数据一条一条插入,拿出错误的数据,保证其他数据可以插入。
2、表单属性校验注解
@NotNull CharSequence, Collection, Map 和 Array 对象不能是 null, 但可以是空集(size = 0)
@NotEmpty CharSequence, Collection, Map 和 Array 对象不能是 null 并且相关对象的 size 大于 0
@NotBlank String 不是 null 且去除两端空白字符后的长度(trimmed length)大于 0
String name = null;
@NotNull: false
@NotEmpty: false
@NotBlank: false
String name = "";
@NotNull: true
@NotEmpty: false
@NotBlank: false
String name = " ";
@NotNull: true
@NotEmpty: true
@NotBlank: false
String name = "Great answer!";
@NotNull: true
@NotEmpty: true
@NotBlank: true
在看到有些代码,对list的校验会是这样的。其实NotEmpty已经是会在基于是否为空的基础上再做其他的判断,只需要有NotEmpty注解即可。
@NotNull
@NotEmpty
private List<UUID> contactUserIds;
3、数据库索引问题
有一张contact表,建了两个索引
- 索引1 是以user_id列建索引
- 索引2 是以contact_user_id列建索引
select *
from contact
where user_id = xxx and contact_user_id = yyy
使用explain对上述sql语句进行解析时,会看到会使用上索引1、索引2。
看到上面的做法你一定会想用联合索引不就更好吗?但是假如你还会使用下面这样的查询sql语句,就使不上联合索引。
select *
from contact
where contact_user_id = yyy
那你一定专门会为contact_user_id属性声明一个索引。那么问题来了,下面的sql语句是会使用上联合索引,还是分别使用索引1、索引2呢?
select *
from contact
where user_id = xxx and contact_user_id = yyy
我们explain对上述sql语句进行解析,是会使用上联合索引的(大家可以自行试试)。
其他情况: 使用in关键字当有索引时,是会使用上索引的。比如我们有联合索引(user_id, contact_user_id)字段。比如下面三种情况都是会使用联合索引。
select *
from contact
where user_id in (xxx) and contact_user_id = yyy
select *
from contact
where user_id = xxx and contact_user_id in (yyy)
select *
from contact
where user_id in (xxx) and contact_user_id in (yyy)
总结
- 当有两个属性列联合查询,应使用这两个属性列作为联合索引,而不是单独建立索引。即使有最左匹配原则,那么则可以为第二个属性列单独建立索引即可。
- in关键字是会使用上索引的。因此JPA的一些findByUserIdIn(List userIds)可以放心使用而不用担心查询性能。
4、UUID数据库使用binary存储原因
- UUID长度问题: UUID长度为36个字符。使用UUID作为不仅会是主键的尺寸很大,而且会使二级索引的尺寸变大,原因是MySQL中的二级索引的value存的是PRIMARY KEY。由于主键和二级索引的尺寸很大,所以不利于在内存中操作
使用BINARY(16), 这个二进制形式数据类型使用16个字节,比人类可读形式(“文本”形式)使用的VARCHAR(36)小的多
5、MySQL表最佳数据量存储误区
其他说法
- 曾广为流传的一个说法:mysql单表数据量超过2000万行,性能会明显下降,当年的百度DBA测试mysql性能时发现,当单表数据量在2000万行量级的时候,SQL操作性能急剧下降,因此结论由此而来。
- 阿里巴巴《java开发手册》提出单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表
事实上,这个数值和实际记录的条数无关,而与mysql的配置以及机器的硬件有关,mysql为了提高性能,会将表的索引装载到内存中,InnoDB buffer size 足够的情况下,其能完成全加载进内存,查询不会有问题。但是当单表数据达到某个量级的时候,导致内存无法存储其索引,使得之后的SQL查询会产生磁盘IO,从而导致性能下降。
6、分表分库实践方案
TODO(后续补上)