所谓跨库join,指的就是数据分散在不同的数据库中,但是又需要做关联查询,这时候就需要解决跨库join的问题。
举个例子,比如我们有两个数据库一个交易trade库,一个用户customer库,这时候,如果我们想join查询位于trade库的orders表和位于customer库的users表,因为他们是不同的数据库,是没办法直接用以下SQL进行join的:
select orders.order_id,users.user_name
from orders
join users
on orders.user_id = users.id
那么,如果要解决这个问题,可以有以下几个方案。
指定库名join
虽然交易trade库和用户customer库是不同的数据库,但是如果他们位于同一个数据库实例中,也是可以进行跨库join的,只需要在JOIN语句中通过指定数据库名来引用它们。例如,在MySQL中:
select orders.order_id,users.user_name
from trade.orders
join customer.users
on trade.orders.user_id = customer.users.id
但是这个要求就是他们必须属于同一个数据库实例。
但是,需要注意的是,跨库JOIN操作可能会影响查询性能,尤其是在处理大量数据时。优化查询和考虑索引的使用可以帮助改善性能。
数据冗余
在上面这篇文章中,我们提及过,有的时候,我们为了避免join,会在数据库表设计的时候,考虑反范式,即做一些字段的冗余。
比如上面的那个例子,我们可以在orders表中把user_name字段冗余下来,这样在一些需要在展示订单的时候展示用户名称的时候就可以不用join了。
但是,这么做也会带来一定的数据一致性的问题,那就是当users表中的用户改了名字,就会导致orders表中的用户名不一致,这时候就需要考虑同时修改,但是如果订单量很大,改起来是成本很高的。
所以,一般来说历史的数据就不改了,这种场景来说,如果用户看自己之前的订单,发现是自己之前的昵称,也是可以接受的。
但是不管怎么说,这个方案毕竟有限制,会带来数据不一致的问题。如果要考虑一致性,就会带来级联修改的问题。
但是,这个方案确实是公司里面用的比较多的。很多时候对于一些不常修改的字段,做一些数据冗余是非常方便的,比如用户的真实姓名。
内存中做join
所谓在内存中做join,其实就是在应用程序中,通过写代码做join。比如:
//先从数据库中查询出要查询的订单列表
List<OrderDO> orders = getOrders();
for(OrderDO orderDO : orders){
OrderDTO orderDTO= new OrderDTO(orderDO);
//根据用户ID去users表查询用户名
String userName = getUserNameByUserId(orderDO.getuserId);
orderDTO.setUserName(userName);
}
这种做法非常多,虽然这么做,代码会比较复杂,然后需要多次查询,但是对于那些没有数据冗余,又没有很复杂的join的场景,还是非常常用的。
这个方案的缺点就是不太适合多张表join的场景,并且这种方案也需要比较大的内存来进行操作,用不好可能会导致内存溢出。
宽表
使用宽表也可以解决跨库JOIN问题,这是一种常用的数据仓库和大数据处理方法。宽表本质上是一种数据预处理技术,它通过将来自不同来源的数据集成到一个单独的、拥有多个字段的大表中,来避免实时跨库JOIN操作的需要。
比如我有多张表需要进行join,那么我们其实可以提前把他们join好,然后把他们保存在宽表中,这样在查询的时候直接到宽表中查就行了。
这就需要我们有技术手段将不同数据库或表中的数据整合到一个大型的宽表中。这个过程通常涉及数据抽取、转换(ETL)和加载(ELT)。
这个方案和做数据冗余差不多,并且是把冗余发挥到了极致。他的优点就是因为所有数据都在同一张表中,所以不需要join了,查询也会更快一些。
但是缺点也比较明显,首先就是宽表的维护是比较复杂的,并且数据冗余就会导致更多的存储空间,以及前面提到的一致性的问题。
这个方案一般都是用在数仓、大数据分析、以及BI报表等场景中。平常开发场景用的并不是特别的多。
第三方数据库
还有一种比较典型的做法,那就是我们把跨库的这些表,想办法放到同一个库中,这样就不存在跨库join了,就变成普通的join了。
所以我们可以把数据同步到一个第三方的数据库中,这个库可以使mysql,也可以是一些数据分析相关的数据库,比如AnalyticDB这种数据库。
比如,我们比较常用的就是通过一些数据同步的手段,比如binlog监听,flink等把数据同步到ADB中,然后一个部门的数据都放在同一个数据库实例中,这样就可以在同一个实例中做多表join了。
这个方案的缺点就是会存在一定的数据延迟。比较适合用在数据对账,数据分析,报表监控等场景。
搜索引擎
使用Elasticsearch等搜索引擎,也是可以解决跨库JOIN的问题的,尤其是在处理大数据和复杂搜索场景时。
Elasticsearch是一个基于Lucene构建的强大的全文搜索和分析引擎,它能够处理大量的数据并提供快速的搜索能力。它适用于那些需要快速、复杂搜索和数据分析的场景。
我们可以基于前面的宽表的思想,把orders表和users中我们关心的所有字段做成一个文档,如类似以下形式:
{
"userId": "123",
"userName": "Hollis",
"orders": [
{
"orderId": "a1",
"orderDate": "2021-01-01",
"amount": 100
},
{
"orderId": "b2",
"orderDate": "2021-02-01",
"amount": 150
}
]
}
然后再基于canal等工具,把orders表及users表的变更同步到ES中,这样我们就可以基于ES直接做查询了。