有时候,我们的分页查询接口的查询结果,需要join多张表才能查出数据。比如在查询商品信息时,需要根据商品名称、单位、品牌、分类等信息查询数据。这时候写一条sql可以查出想要的数据

有时候,我们的分页查询接口的查询结果,需要join多张表才能查出数据。比如在查询商品信息时,需要根据商品名称、单位、品牌、分类等信息查询数据。这时候写一条sql可以查出想要的数据,比如下面这样的:select 
  p.id,
  p.product_name,
  u.unit_name,
  b.brand_name,
  c.category_name
from product p
inner join unit u on p.unit_id = u.id
inner join brand b on p.brand_id = b.id
inner join category c on p.category_id = c.id
where p.name='测试商品' 
limit 0,20;
使用product表去join了unit、brand和category这三张表。其实product表中有unit_id、brand_id和category_id三个字段。我们可以先查出这三个字段,获取分页的数据缩小范围,之后再通过主键id集合去查询额外的数据。我们可以把sql改成这样:select 
  p.id,
  p.product_id,
  u.unit_id,
  b.brand_id,
  c.category_id
from product
where name='测试商品'
limit 0,20;
这个例子中,分页查询之后,我们获取到的商品列表其实只要20条数据。再根据20条数据中的id集合,获取其他的名称,例如:select id,name 
from unit
where id in (1,2,3);
然后在程序中填充其他名称。伪代码如下:List<Product> productList = productMapper.search(searchEntity);
List<Long> unitIdList = productList.stream().map(Product::getUnitId).distinct().collect(Collectors.toList());
List<Unit> unitList = UnitMapper.queryUnitByIdList(unitIdList);
for(Product product: productList) {
   Optional<Unit> optional = unitList.stream().filter(x->x.getId().equals(product.getId())).findAny();
   if(optional.isPersent()) {
      product.setUnitName(optional.get().getName());
   } 
}
这样就能有效的减少join表的数量,可以一定的程度上优化查询接口的性能

 

优化方法确实是一种常见的性能优化技术,尤其是在涉及复杂的 JOIN 查询时。通过减少多表联合查询,采用两步查询的方式,可以有效减少数据库的压力,并提高系统的性能。下面我会进一步阐述这个优化方法,并总结一些需要注意的细节。

### 优化分析

1. **多表 JOIN 查询的性能瓶颈**:
   - 在一个复杂的查询中,当有多个表通过 `JOIN` 连接时,数据库需要在多个表之间做连接操作,这会导致查询的效率较低,尤其是在表数据量大的时候。每个表的索引、连接条件、数据分布等因素都可能影响查询效率。
   - 对于分页查询,当 `LIMIT` 被用于分页时,数据库在执行查询时需要根据 `WHERE` 条件和 `LIMIT` 来确定要检索的行。对于大表的多表连接,尤其是当 JOIN 的表很大时,查询的性能往往会受到显著影响。

2. **通过两步查询优化**:
   - **第一步**:首先从 `product` 表中查询出需要的字段(比如 `unit_id`、`brand_id` 和 `category_id`),并应用分页。由于这些字段只是简单的 ID,查询相对较快。
   - **第二步**:再通过这些 ID 去查询 `unit`、`brand`、`category` 等其他表的详细信息。因为此时这些查询仅仅是根据 ID 列表查数据,而非多表连接查询,通常来说,这样的查询会比复杂的 JOIN 查询效率更高。

### 优化过程

1. **查询商品的基本信息**:
   ```sql
   SELECT
       p.id,
       p.product_name,
       p.unit_id,
       p.brand_id,
       p.category_id
   FROM
       product p
   WHERE
       p.name = '测试商品'
   LIMIT 0, 20;
   ```
   这条查询只会返回 20 条记录,且只包含 `product_id`、`unit_id`、`brand_id` 和 `category_id` 等字段。这样可以有效减少不必要的 JOIN 操作,提高查询性能。

2. **获取相关表的数据**:
   根据上面查询结果中返回的 `unit_id`、`brand_id` 和 `category_id` 列表,进行分别查询:
   
   ```sql
   SELECT id, unit_name FROM unit WHERE id IN (1, 2, 3);
   SELECT id, brand_name FROM brand WHERE id IN (1, 2, 3);
   SELECT id, category_name FROM category WHERE id IN (1, 2, 3);
   ```
   这些查询会返回分别对应 `unit`、`brand` 和 `category` 的名称信息,这些表的数据量相对较小,因此查询效率较高。

3. **程序中的数据填充**:
   在程序中,首先查询出商品列表,并且通过 `unit_id`、`brand_id` 和 `category_id` 获取对应的详细信息。然后,你可以通过以下伪代码填充商品信息:
   
   ```java
   List<Product> productList = productMapper.search(searchEntity);
   
   // 获取所有不同的 unit_id
   List<Long> unitIdList = productList.stream().map(Product::getUnitId).distinct().collect(Collectors.toList());
   List<Unit> unitList = UnitMapper.queryUnitByIdList(unitIdList);
   
   // 获取所有不同的 brand_id
   List<Long> brandIdList = productList.stream().map(Product::getBrandId).distinct().collect(Collectors.toList());
   List<Brand> brandList = BrandMapper.queryBrandByIdList(brandIdList);
   
   // 获取所有不同的 category_id
   List<Long> categoryIdList = productList.stream().map(Product::getCategoryId).distinct().collect(Collectors.toList());
   List<Category> categoryList = CategoryMapper.queryCategoryByIdList(categoryIdList);
   
   // 填充 unitName、brandName、categoryName
   for (Product product : productList) {
       Optional<Unit> optionalUnit = unitList.stream().filter(x -> x.getId().equals(product.getUnitId())).findFirst();
       if (optionalUnit.isPresent()) {
           product.setUnitName(optionalUnit.get().getUnitName());
       }
       
       Optional<Brand> optionalBrand = brandList.stream().filter(x -> x.getId().equals(product.getBrandId())).findFirst();
       if (optionalBrand.isPresent()) {
           product.setBrandName(optionalBrand.get().getBrandName());
       }
       
       Optional<Category> optionalCategory = categoryList.stream().filter(x -> x.getId().equals(product.getCategoryId())).findFirst();
       if (optionalCategory.isPresent()) {
           product.setCategoryName(optionalCategory.get().getCategoryName());
       }
   }
   ```

### 优化效果

- **减少了 JOIN 操作**:通过先查询商品 ID 列表,避免了复杂的多表连接。数据库查询只涉及简单的 ID 查询,性能上有了显著提高。
- **分页查询的速度提升**:`LIMIT` 限制查询的结果数量,可以显著减少每次查询需要处理的数据量,尤其在数据量大的时候,查询响应时间会更短。
- **减少了网络传输的数据量**:在传统的 JOIN 查询中,数据库会返回所有需要的列,包括大量的字段信息,而通过这种分步查询方法,第一次查询仅返回 ID 列表,第二次查询仅返回名称信息,减少了网络传输的数据量。

### 注意事项

1. **数据一致性**:如果你的数据表在查询过程中发生了变化(例如插入或删除了某些记录),可能会影响到查询的结果。在这种情况下,使用这种两步查询的方法可能会导致不一致的问题。可以考虑使用事务保证一致性,或者在查询时做数据锁定。
   
2. **查询效率**:虽然分步查询能减少复杂的 JOIN 操作,但仍然需要注意,第二步查询依然会涉及多个 `IN` 子句(尤其是在 `IN` 列表较长时),这可能会影响查询性能。可以考虑分页的方式进一步优化查询,分批次查询相关数据。

3. **程序中的性能问题**:在程序中,你可能会面临遍历大量数据进行匹配的性能问题。虽然使用了 `distinct` 来避免重复查询,但对于非常大的数据集,这种操作也可能带来一定的性能瓶颈。可以通过进一步优化数据存储和查询方式来减少这种影响。

### 总结

你提出的这种优化方法是针对复杂的多表 `JOIN` 查询的常见性能优化技巧,尤其适用于数据量较大或者查询复杂度较高的场景。通过分步查询和批量处理,可以有效减少数据库的负担,并提高整体的查询性能。当然,具体优化效果也需要根据实际情况进行性能测试和调整。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值