Generator danych do bazy - zastąpienie executeQuery()

Odpowiedz Nowy wątek
2020-03-25 15:36

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

0

Cześć, piszę aktualnie program w Javie, który ma generować dane do bazy. Ogólnie kończę już pracę nad tym, jednak mam jeszcze jeden problem... W zasadzie jest to chyba ostatnia rzecz, którą muszę dokończyć. Może przejdę do problemu.
W bazie mam tabelę z towarami oraz jest też tabela pozycja faktury.
W tabeli pozycja faktury mam do zapełnienia pola takie jak: ilość danego towaru. Żeby generator generował realne dane, takie jakie mogłyby być w rzeczywistości to muszę najpierw sprawdzić, ile danego towaru jest na stanie zanim wylosuję liczbę.
Działa to u mnie tak:

  1. Losuję sobie id z tabeli towary
  2. Sprawdzam ilość sztuk na magazynie danego towaru
  3. Losuję ilość sztuk, tak żeby nie przekroczyć faktycznej wartości.
    Wszystko fajnie pięknie mam zrobione. Jednak w czym jest problem? A w tym, że przez metodę, która sprawdza ilość sztuk na magazynie danego towaru, 1000 wierszy do około 10 tabel wstawia się ponad 7 minut. Bez tej metody jest to 10 sekund. Czyli sama metoda wykonuje się jakieś 7 minut.
    A dlaczego się tak dzieje?
    Dzieje się tak, dlatego że metodę wykonuję w pętli. Czyli 1000x wykonuje się executeQuery(). I tutaj nie wiem jak sobie z tym poradzić.
    Przy insertach nie było problemu, po prostu używałem metody addBatch(), a po zakończonej pętli executeBatch().
    Ale przy selectach o ile wiem nie da się zrobić czegoś takiego.
    Macie jakieś pomysły jak można by rozwiązać ten problem?

Poniżej znajduje się metoda na sprawdzenie tej ilości sztuk na magazynie. Wywoływana jest ona w pętli, przykładowo dla 1000 wykonań, metoda wykonuje się jakieś 7 minut.

public static int Sprawdz_ilosc_magazynowa_towaru(Connection baza, int klucz)
    {
        int ilosc_magazynowa = 0;

        try
        {
            PreparedStatement prepared = baza.prepareStatement("select ilosc_magazynowa from Towary where id_towaru="+klucz);
            prepared.setFetchSize(4001);
            ResultSet wyniki = prepared.executeQuery();
            if(wyniki.next()) ilosc_magazynowa = wyniki.getInt("ilosc_magazynowa");

            wyniki.close();
            prepared.close();

        }
        catch(Exception e)
        {

        }

        return ilosc_magazynowa;
    }
edytowany 1x, ostatnio: annonymouzinho, 2020-03-25 15:49
Wbrew pozorom preparowania - powinieneś z tego mieć zysk - ale nie masz, bo nie używasz parametru, tylko sklejasz. - AnyKtokolwiek 2020-03-25 15:41
Ale muszę to sklejać. Dla każdego towaru ilość jest inna, więc w zależności jaki to był towar muszę pobrać dla niego ilość sztuk, która znajduje się na magazynie. - annonymouzinho 2020-03-25 15:51
Próbowałem, absolutnie nic to nie zmienia - annonymouzinho 2020-03-25 16:02
oczywiście przed pętlą? - AnyKtokolwiek 2020-03-25 16:02
Tak, przed pętlą stworzyłem string z selectem. Następnie w pętli wywołałem metodę, dodałem do niej tego strina jako dodatkowy parametr. I w statemencie teraz mam cos takiego: (polecenie+klucz); - annonymouzinho 2020-03-25 16:06

Pozostało 580 znaków

2020-03-25 16:33

Rejestracja: 3 lata temu

Ostatnio: 1 minuta temu

Lokalizacja: U krasnoludów - pod górą

1

IMO problem jest poza metodą. Faktycznie jest nieoptymalne użycie preparedStatement, ale w praktyce nie ma to zwykle dużego znaczenia jesli chodzi o szybkość (dla bezpieczeństwa natomiast ma...).
Strzelam, że zła obsługa connection lub coś podobnego. Trzeba by całą pętle zobaczyć.
Ewentualnie brak indeksu na id_towary - dośc nietypowe (klucz główny), ale nauka radziecka notowała takie przypadki.

Poza tym - jak chcesz mieć szybko to nie czytaj z bazy. Ja tam dane trzymam w RAM - i szybko, i pewnie.


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.
edytowany 4x, ostatnio: jarekr000000, 2020-03-25 16:37
A indeksów jeszcze nie mam. Jest to dopiero następne zadanie. Teraz mieliśmy zrobić generator, w kolejnym zadaniu będziemy testować duże zapytania i sprawdzać jak długo się wykonują, a ile się będą wykonywały po dodaniu indeksów. - annonymouzinho 2020-03-25 16:45
@jarekr000000: Zawsze polecasz trzymać dane w RAM, ale co z jego persystencją? Brak prądu, restart serwera i elo z danych - Pinek 2020-03-25 20:00
Mam ups, a przycisk reset zalepiłem. Dane bezpieczne. - jarekr000000 2020-03-25 20:05
xd a na serio? - Pinek 2020-03-25 21:08
A na serio to w każdym systemie inaczej, w zależności od warunków. Ale w bazie danych dane trzymam tylko jak nie ma innego wyjścia. Najlepiej w event log + RAM, czasem inne systemy + RAM, czasem baza danych insert only. A czasem jak wszyscy - w tabelkach - jak to mało wazny system o niewielkim ruchu. - jarekr000000 2020-03-25 22:07
https://github.com/OpenHFT/Chronicle-Queue na Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz jestem w stanie zapisać 10 mln 40 bajtowych wiadomości (replikując jeden raz) na sekundę. Zdaję sobię sprawę, że nie każdy będzie real time liczył kursy walut ale możemy skorzystać z bardziej high-level api (nawet coś z jsonem zdaje się jest) i nadal otrzymać zadowalający speed bez kolosa typu relacyjna baza danych - pedegie 2020-03-27 05:29

Pozostało 580 znaków

2020-03-25 16:43

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

0

To jest zadanie z aplikacji bazodanowych, więc całe zadanie opiera się właśnie na odczycie i zapisie danych do bazy danych.
A jeśli chodzi o błąd poza pętlą, to szczerze wątpię żeby tak było. Przetestowałem już wcześniej tą metodę poza pętlą, i kiedy wykonuje się tylko jeden raz to wszystko działa sprawnie.

Tutaj metoda połączenia:

Class.forName("oracle.jdbc.driver.OracleDriver");
Connection baza = DriverManager.getConnection("jdbc:oracle:thin:@IP:orcltp", "nazwa", "haslo");

Wywołanie pętli:

for(int i = 0; i <ilosc_wierszy; i++)
        {    
            Wstaw_dane_do_tabeli_pozycja_faktury(baza, insert_pozycja_faktury);
        }

Metoda Wstaw_dane_do_tabeli_pozycja_faktury, która wywołuje metodę sprawdzającą stan na magazynie:

public static void Wstaw_dane_do_tabeli_pozycja_faktury(Connection baza, PreparedStatement tabela)
    {
        int id_faktury = Wylosuj_unikalny_klucz_obcy();
        int ile_pozycji = Losuj_liczbe_z_przedzialu(1,5);

        try
        {
            for(int i =0;i<ile_pozycji;i++)
            {
                int klucz = Wylosuj_klucz_obcy(klucze_obce_dwa);
                int max = Sprawdz_ilosc_magazynowa_towaru(baza, polecenie, klucz);
                int ilosc;
                if(max>=99) ilosc = Losuj_liczbe_z_przedzialu(1, 99);
                else ilosc = Losuj_liczbe_z_przedzialu(1, max);

                tabela.setString(1, null);
                tabela.setInt(2, id_faktury);
                tabela.setInt(3, klucz);
                tabela.setInt(4, ilosc);
                //tabela.setString(5, Policz_cene_pozycji(baza, klucz, ilosc));
                tabela.setString(5, "200,00");
                tabela.addBatch();
            }
        }
        catch(Exception e)
        {
            System.out.println(e);
        }

    }

Oraz aktualna metoda do sprawdzenia tej ilości (selecta dałem jako parametr, tak jak mi wcześniej doradzano):

public static int Sprawdz_ilosc_magazynowa_towaru(Connection baza, String polecenie, int klucz)
    {
        int ilosc_magazynowa = 0;

        try
        {
            PreparedStatement prepared = baza.prepareStatement(polecenie+klucz);
            prepared.setFetchSize(4001);
            ResultSet wyniki = prepared.executeQuery();
            if(wyniki.next()) ilosc_magazynowa = wyniki.getInt("ilosc_magazynowa");

            wyniki.close();
            prepared.close();

        }
        catch(Exception e)
        {

        }

        return ilosc_magazynowa;
    }
Dlaczego tabela.setString(5, "200,00"); ? Kolejny argument, że z projektem bazy jest coś nie-ten-tego - AnyKtokolwiek 2020-03-25 20:15
Bo sobie testowałem na stałych wartościach jak tą metodę zakomentowałem i póki co tak zostawiłem. Ale to nie ma żadnego znaczenia. - annonymouzinho 2020-03-25 20:17

Pozostało 580 znaków

2020-03-25 16:58
Moderator

Rejestracja: 16 lat temu

Ostatnio: 16 minut temu

4

Masakra. Po pierwsze naucz się uzywać prepared statementów a nie klej stringów jak zwierze. Podpowiem select ilosc_magazynowa from Towary where id_towaru = ? a następnie zobacz jakie metody ma klasa PreparedStatement. Zrób taki statement RAZ a potem binduj parametry i odpalaj w pętli.


Masz problem? Pisz na forum, nie do mnie. Nie masz problemów? Kup komputer...
edytowany 1x, ostatnio: Shalom, 2020-03-25 16:59

Pozostało 580 znaków

2020-03-25 17:27

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

0

Zrobione, teraz metoda wygląda tak:

public static int Sprawdz_ilosc_magazynowa_towaru(Connection baza, int klucz)
    {
        int ilosc_magazynowa = 0;

        try
        {  
            pobierz_sztuki.setInt(1, klucz);
            pobierz_sztuki.setFetchSize(4001);
            ResultSet wyniki = pobierz_sztuki.executeQuery();
            if(wyniki.next()) ilosc_magazynowa = wyniki.getInt("ilosc_magazynowa");

            wyniki.close();
            pobierz_sztuki.close();

        }
        catch(Exception e)
        {

        }

        return ilosc_magazynowa;
    }

Tylko kurcze teraz mi się coś spieprzyło w losowaniu, bo wywala mi poniższy błąd. Do bazy zawsze wstawia się tylko jeden wiersz, przy drugim się wysypuje i leci aż do końca.

java.lang.IllegalArgumentException: bound must be positive

Tak jakbym losował liczbę ze złego przedziału.
Ale chciałem przetestować czy ta zmiana przyspieszy program, więc ustawiłem sobie stałą wartość dla ilości. Faktycznie, program wykonuje się szybciej, ale jednak nadal dosyć długo to trwa - 4 minuty.
Chyba, że nadal coś mam źle w tej metodzie?

EDIT:
Dobra zapomniałem z metody wywalić zamknięcie tego statementu. Teraz działa, ale jeśli chodzi o czas, to nadal wygląda to tak samo. Czas wykonania metody to około 7 minut...

edytowany 3x, ostatnio: annonymouzinho, 2020-03-25 17:38
Dobra, błąd pewnie przez to, że zamykam statement. - annonymouzinho 2020-03-25 17:28

Pozostało 580 znaków

2020-03-25 19:09
Moderator

Rejestracja: 16 lat temu

Ostatnio: 16 minut temu

1

No dobra, ale teraz pytanie co konkretnie trwa tutaj dużo czasu? Może oprócz tego selecta robisz jeszcze jakis dziwne rzeczy, albo robisz głupoty w stylu lock na tabeli i nie zwalnianie transakcji.
Moja rada: zrób minimalny przykład z jakimś H2 który pokazuje twój problem.


Masz problem? Pisz na forum, nie do mnie. Nie masz problemów? Kup komputer...
edytowany 1x, ostatnio: Shalom, 2020-03-25 19:09
To oracle, więc na h2 pewnie będzie śmigać :-) - jarekr000000 2020-03-25 19:11
tak też może być :D - Shalom 2020-03-25 19:15

Pozostało 580 znaków

2020-03-25 19:16

Rejestracja: 3 lata temu

Ostatnio: 1 minuta temu

Lokalizacja: U krasnoludów - pod górą

1

A napisz coś może o tym gdzie ta baza stoi? na tym samym komputerze, w tej samej sieci?
Na pewno masz indeksy?
Masz jakiś monitoring javy i tego oracla? CPU/ IO ? Cokolwiek?

Pomysł : zamiast pytania wstaw select 1 from dual i zobacz jak się zmieni wydajność.
Jeśli nadal będzie wolno to raczej nie z powodu braku indeksów czy locków.


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.
edytowany 1x, ostatnio: jarekr000000, 2020-03-25 19:19

Pozostało 580 znaków

2020-03-25 19:24

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

0

Baza danych jest uczelniana. Każdy ze studentów ma tu swoje konto. Nie jest lokalna, nie jest w tej samej sieci.
Indeksów nie mam, indeksy mamy dopiero zrobić w następnym zadaniu.
Nie mam monitoringu.
Ale według mnie wszystko jest przez linijkę

ResultSet wyniki = pobierz_sztuki.executeQuery();

Jak zacząłem robić ten program to cały czas dawałem executeQuery. I wszystko wykonywało się po 17 minut. Zacząłem szukać dlaczego tak długo to trwa, natrafiłem na addBatch. No i zastąpiłem executeQuery na addBatch, a dopiero jak wychodziłem z pętli to robiłem executeBatch i nagle z 17 minut wszystko trwało 10 sekund.
Teraz mam podobną sytuację, tylko w przypadku selecta nie robi się czegoś takiego jak addBatch, prawda? Czy się mylę?
Tylko w tej metodzie mam executeQuery. Jeśli ją zakomentuję, program wykonuje się w 8-12 sekund. Jeśli zostawię - ponad 7 minut.

Pozostało 580 znaków

2020-03-25 19:26

Rejestracja: 3 lata temu

Ostatnio: 1 minuta temu

Lokalizacja: U krasnoludów - pod górą

1

W takim razie odpowiedź jest prosta: masz lagi ;-)


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.

Pozostało 580 znaków

2020-03-25 19:45

Rejestracja: 1 rok temu

Ostatnio: 22 godziny temu

0
select 1 from dual

nic nie zmienia :/ Dalej trwa tak długo.

Pozostało 580 znaków

2020-03-25 20:42

Rejestracja: 3 lata temu

Ostatnio: 1 minuta temu

Lokalizacja: U krasnoludów - pod górą

1

W takim razie odpowiedź jest prosta: masz lagi ;-)


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.
Ale jak mam lagi? I dlatego metoda wykonuje się 7 minut? Przecież wtedy reszta rzeczy też by się strasznie długo robiła - annonymouzinho 2020-03-25 20:44
lagi na połączeniu do bazy danych. każde execute trwa relatywnie długo. Jak robisz batchUpdate to zbierasz x wywołań w jedno i płacisz mniejszą "karę". Tak po prawdzie to zgaduje. To że problemem jest łacze z bazą danych wydaje się w tej chwili najbardziej prawdopodobnym wyjaśnieniem, ale niekoniecznie musi tak być (tylko Ty chyba nie masz narzędzi, żeby sprawdzić gdzie jest problem). - jarekr000000 2020-03-25 20:48

Pozostało 580 znaków

Odpowiedz

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0