[JAVA] Évitez autant que possible l'accès à la base de données à partir de boucles

Comme le dit le titre.

Comme ça


//Arrete ca
List<Person> people = new ArrayList<>();
for (int id : ids) {
    Person person = repository.findOne(p);
    people.add(person);
}

//Faisons cela
List<Person> people = repository.findAll(ids);

C'est une évidence, mais j'ai l'impression que c'est un code commun aux débutants. Dans ma plage d'observation étroite, j'ai déjà SQL pour obtenir un cas, donc je devrais l'utiliser, ou parce que la logique de traitement est plus simple pour obtenir un cas à la fois, etc. ** [Move] * Quand il est fait avec *, ce n'est pas si perceptible, et on peut voir que cela entraînera des problèmes de performances plus tard.

Mesurons la vitesse avec un simple exemple de code. L'exemple de code est Spring Boot 2.0 + Spring Data JPA.

Effectuez une boucle 10 000 fois, accédez à la base de données à partir de la boucle et obtenez 10 000 enregistrements, puis utilisez la clause IN pour préparer et appeler le processus afin d'obtenir 10 000 enregistrements à la fois. Comme il était difficile de créer un écran, j'en ai fait un RestController et l'ai affiché en JSON de manière appropriée.

Code de vérification

Controller


package com.example.web;

import com.example.domain.service.BenchmarkService;
import com.example.domain.service.BenchmarkServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/benchmark")
@RequiredArgsConstructor
public class SandboxRestController {

    private final BenchmarkService benchmarkService;

    @RequestMapping("/sql/loop")
    public ResponseEntity<String> benchmarkDatabaseAccess() {
        //10 à la fois en utilisant la clause IN,Obtenez 000 enregistrements
        long beforeOneTime = System.currentTimeMillis();
        benchmarkService.oneTimesDatabaseAccess();
        long afterOneTime = System.currentTimeMillis();

        // 10,Obtenez des enregistrements une à une sur 000 boucles
        long beforeLoopTime = System.currentTimeMillis();
        benchmarkService.tenThousandTimesDatabaseAccess();
        long afterLoopTime = System.currentTimeMillis();

        Map<String, Long> result = new HashMap<>();
        result.put("oneTimeFirst", afterOneTime - beforeOneTime);
        result.put("loopTimeFirst", afterLoopTime - beforeLoopTime);

        //Refaites le même processus
        beforeOneTime = System.currentTimeMillis();
        benchmarkService.oneTimesDatabaseAccess();
        afterOneTime = System.currentTimeMillis();

        beforeLoopTime = System.currentTimeMillis();
        benchmarkService.tenThousandTimesDatabaseAccess();
        afterLoopTime = System.currentTimeMillis();

        result.put("oneTimeSecond", afterOneTime - beforeOneTime);
        result.put("loopTimeSecond", afterLoopTime - beforeLoopTime);
        return ResponseEntity.status(200).body(result.toString());
    }
}

Service


@Service
@RequiredArgsConstructor
public class BenchmarkServiceImpl implements BenchmarkService {

    private final BenchmarkRepository benchmarkRepository;

    public void tenThousandTimesDatabaseAccess() {
        for (int i = 1; i <= 10000; i++) {
            benchmarkRepository.findById(i);
        }
    }

    public void oneTimesDatabaseAccess() {
        List<Integer> ids = IntStream.range(1, 10000).boxed().collect(toList());
        benchmarkRepository.findByIdIn(ids);
    }
}

Pour la base de données, utilisez la table préparée facilement par Spring Data JPA.

Entity


package com.example.domain.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

@Entity
@Data
@Table(name = "benchmark")
public class BenchmarkEntity {

    public BenchmarkEntity() {};

    public BenchmarkEntity(int id, int num) {
        this.id = id;
        this.num = num;
    }

    @Id
    @GeneratedValue
    private Integer id;

    @NotNull
    private int num;
}

Repository


package com.example.domain.repository;

import com.example.domain.model.BenchmarkEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface BenchmarkRepository extends JpaRepository<BenchmarkEntity, Integer> {

    // SELECT * FROM benchmark WHERE id = #{id}
    BenchmarkEntity findById(int id);

    // SELECT * FROM benchmark WHERE id IN (#{...ids})
    List<BenchmarkEntity> findByIdIn(List<Integer> ids);
}

résultat

Quand je l'ai utilisé en mémoire avec h2database

10000 boucles premières 711 ms, secondes 438 ms L'acquisition en bloc est de 393 ms pour la première fois et de 24 ms pour la deuxième fois

C'était ce genre de sentiment. Même en mémoire fait une telle différence, donc la différence augmentera à mesure que le temps de lecture par lecture augmente, par exemple, pour accéder à une base de données dans un autre environnement.

Il est plus facile à traiter si vous obtenez un par un en tant qu'implémentation, et dans le cas de l'acquisition par lots, le traitement ultérieur peut être un peu compliqué, ou il peut prendre du temps pour exécuter une boucle dans ce traitement, mais Dans la plupart des cas, il devrait être plus rapide de les obtenir tous en même temps (à moins que vous n'émettiez du SQL extrêmement peu performant).

Je ne dis pas que nous devrions éliminer complètement le SQL appelé de la boucle, mais faites attention au traitement qui devrait augmenter le nombre de boucles sous une charge importante en raison des exigences.

Référence: https://github.com/tnemotox/sandbox

Recommended Posts

Évitez autant que possible l'accès à la base de données à partir de boucles
Évitez le calendrier Java autant que possible
3. Créez une base de données à laquelle accéder à partir du module Web