为什么有多次调用DB

Why there are multiple calls to DB

我正在使用 Postgre 玩 R2DBC SQL。我正在尝试的用例是通过 ID 以及语言、演员和类别来获取电影。以下是架构

这是ServiceImpl中对应的一段代码

@Override
public Mono<FilmModel> getById(Long id) { 
    Mono<Film> filmMono = filmRepository.findById(id).switchIfEmpty(Mono.error(DataFormatException::new)).subscribeOn(Schedulers.boundedElastic());
    Flux<Actor> actorFlux = filmMono.flatMapMany(this::getByActorId).subscribeOn(Schedulers.boundedElastic());
    Mono<String> language = filmMono.flatMap(film -> languageRepository.findById(film.getLanguageId())).map(Language::getName).subscribeOn(Schedulers.boundedElastic());
    Mono<String> category = filmMono.flatMap(film -> filmCategoryRepository
                    .findFirstByFilmId(film.getFilmId()))
            .flatMap(filmCategory -> categoryRepository.findById(filmCategory.getCategoryId()))
            .map(Category::getName).subscribeOn(Schedulers.boundedElastic());

    return Mono.zip(filmMono, actorFlux.collectList(), language, category)
            .map(tuple -> {
                FilmModel filmModel = GenericMapper.INSTANCE.filmToFilmModel(tuple.getT1());
                List<ActorModel> actors = tuple
                        .getT2()
                        .stream()
                        .map(act -> GenericMapper.INSTANCE.actorToActorModel(act))
                        .collect(Collectors.toList());
                filmModel.setActorModelList(actors);
                filmModel.setLanguage(tuple.getT3());
                filmModel.setCategory(tuple.getT4());
                return filmModel;
            });
         }

日志显示 4 次调用 film

2021-12-16 21:21:20.026 DEBUG 32493 --- [ctor-tcp-nio-10] o.s.r2dbc.core.DefaultDatabaseClient     : Executing SQL statement [SELECT film.* FROM film WHERE film.film_id =  LIMIT 2]
2021-12-16 21:21:20.026 DEBUG 32493 --- [actor-tcp-nio-9] o.s.r2dbc.core.DefaultDatabaseClient     : Executing SQL statement [SELECT film.* FROM film WHERE film.film_id =  LIMIT 2]
2021-12-16 21:21:20.026 DEBUG 32493 --- [ctor-tcp-nio-12] o.s.r2dbc.core.DefaultDatabaseClient     : Executing SQL statement [SELECT film.* FROM film WHERE film.film_id =  LIMIT 2]
2021-12-16 21:21:20.026 DEBUG 32493 --- [actor-tcp-nio-7] o.s.r2dbc.core.DefaultDatabaseClient     : Executing SQL statement [SELECT film.* FROM film WHERE film.film_id =  LIMIT 2]
2021-12-16 21:21:20.162 DEBUG 32493 --- [actor-tcp-nio-9] o.s.r2dbc.core.DefaultDatabaseClient     : Executing SQL statement [SELECT language.* FROM language WHERE language.language_id =  LIMIT 2]
2021-12-16 21:21:20.188 DEBUG 32493 --- [actor-tcp-nio-7] o.s.r2dbc.core.DefaultDatabaseClient     : Executing SQL statement [SELECT film_actor.actor_id, film_actor.film_id, film_actor.last_update FROM film_actor WHERE film_actor.film_id = ]
2021-12-16 21:21:20.188 DEBUG 32493 --- [ctor-tcp-nio-10] o.s.r2dbc.core.DefaultDatabaseClient     : Executing SQL statement [SELECT film_category.film_id, film_category.category_id, film_category.last_update FROM film_category WHERE film_category.film_id =  LIMIT 1]
2021-12-16 21:21:20.313 DEBUG 32493 --- [ctor-tcp-nio-10] o.s.r2dbc.core.DefaultDatabaseClient     : Executing SQL statement [SELECT category.* FROM category WHERE category.category_id =  LIMIT 2]
2021-12-16 21:21:20.563 DEBUG 32493 --- [actor-tcp-nio-7] o.s.r2dbc.core.DefaultDatabaseClient     : Executing SQL statement [SELECT actor.* FROM actor WHERE actor.actor_id =  LIMIT 2]

我并不是要寻求 SQL 优化(连接等)。我绝对可以让它变得更高效。但关键问题是为什么我确实看到 4 SQL 个对 Film table 的查询。只是补充一下,我已经修复了代码。但是无法提前理解核心reason.Thanks

我不是很熟悉你的堆栈,所以这是一个高层次的答案来解决你的“为什么”。将会有一个更具体的答案给你,在管道的某个地方(例如,可以确认 this thread 是否相关的人)。

虽然我不是 Spring Daisy(或 Spring 开发人员),但您将表达式绑定到 filmMono,解析为查询 select film.* from film....。您引用该表达式四次,它在不同的上下文中解析了四次。语句的顺序很可能是 lib 作者延迟计算您在本地绑定的表达式的部分成功尝试,因此它能够对四个意外相同的查询进行批处理。您很可能通过收集到一个实际容器中来解决此问题,然后映射到该容器而不是绑定到 filmMono.

的表达式

一般来说,这种情况是因为当语言本身不支持惰性求值时,库作者可用的选项并不好。因为任何操作都可能改变数据集,库作者必须在以下两者之间做出选择:

  • A,构造足够的脚手架来完整记录所有需要的资源,为任何需要以某种方式改变记录的操作复制数据集,并希望他们能够检测到任何可能泄漏脚手架的边缘情况。已解决的数据集是预期的(得到这个权利是......很难)。
  • B,将每个级别的映射解析为一个查询,对于它出现的每个上下文,以免任何操作以可能让集成商(例如您)感到惊讶的方式改变数据集。
  • C,同上,除了不是复制原始请求,只是复制数据……在每一步。在 JVM 上,复制传递变得非常痛苦,非常快,像 Clojure 和 Scala 这样的语言通过让开发人员非常具体地了解他们是想就地变异,还是复制然后变异来处理这个问题。

在你的例子中,B 对编写该库的人来说最有意义。事实上,他们显然已经足够接近 A,以至于他们能够批处理所有通过解析绑定到 filmMono 的表达式(它们只是偶然地相同)而产生的查询,所以让我印象深刻。

可以重写许多访问模式来优化生成的查询。您的里程数可能会发生变化……变化很大。熟悉原始 SQL,或者像 GraphQL 这样的特殊用途语言,可以提供比关系映射器更一致的结果,但我更加欣赏良好的 IDE 支持,以及混合领域,如这通常意味着放弃自动完成、上下文突出显示、lang-server solution-proofs 和 linting。

考虑到问题的范围是“为什么会发生这种情况?”,甚至注意到我对您的堆栈不熟悉,答案是“用一种本身不支持它的语言进行惰性评估真的很难."

Why I do see 4 SQL queries to Film table.

原因很简单。您订阅了 Mono<Film> 4 次:

Mono<Film> filmMono = filmRepository.findById(id);

Flux<Actor> actorFlux = filmMono.flatMapMany(...); (1)
Mono<String> language = filmMono.flatMap(...); (2)
Mono<String> category = filmMono.flatMap(...); (3)
Mono.zip(filmMono, actorFlux.collectList(), language, category) (4)

filmMono 的每个订阅都会触发一个新查询。请注意,您可以通过使用 Mono#cache 运算符将 filmMono 变成热源并缓存所有四个订阅者的结果来更改它。