2012-10-08 7 views
8

Mam tabeli MySQL, które wygląda następująco:wypełnić MySQL z dużej serii wierszy szybko

MySQL Table: status

SQL do tworzenia struktury jest:

CREATE TABLE `status` (
`id` INT(11) NOT NULL, 
`responseCode` INT(3) NOT NULL DEFAULT '503', 
`lastUpdate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 
PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

To przechowuje unikalne id, responseCode i lastUpdate. responseCode jest kod odpowiedzi HTTP Żądanie: 404, 500, 503, 200, itd.

Mam URL musi odpowiadać każdemu id dla którego robię żądanie HTTP i rekord w tabeli czas zrobiłem żądanie i otrzymana odpowiedź.

Skrypt czyni to zapytanie na stole status:

SELECT id FROM status WHERE lastUpdate < 'XXXX' OR 
(responseCode != 200 AND responseCode != 404) 
ORDER BY id DESC LIMIT 100 

Gdzie XXXX byłoby datę gdzie decydowania że cokolwiek starszy od tej daty musi być odświeżane niezależnie od kodu odpowiedzi. Co więcej, chcę ponownie sprawdzić żądanie HTTP, jeśli nie dostałem 200 lub 404 niezależnie od ostatniej daty lastUpdate. I LIMIT do 100, ponieważ uruchamiam tylko 100 na raz, a potem mam to przez jakiś czas przespać i zrobić kolejne 100 później, i tak dalej.

Tak czy inaczej, to wszystko jest w porządku, ale to, co chcę zrobić, to wypełnić tabelę z wyprzedzeniem z powiedzieć seria tak:

(1, 503, NOW()), (2, 503, NOW()), (3, 503, NOW()) ... (100000, 503, NOW()) 

Wskazówka jedynie identyfikator jest zwiększany, ale niekoniecznie zacznij od 1 dla moich potrzeb. Chcę, aby tabela była już wypełniona, ponieważ wtedy powyższe zapytanie może nadal przyciągać id za te, które musimy ponownie sprawdzić, i nie chciałbym nigdy więcej wstawiać do tabeli status jako id są skończone i nie ulegną zmianie (ale jest ich wiele).

Próbowałem za pomocą Java, (choć PHP, C#, lub czegokolwiek innego, co jest samo pojęcie i nie ma dla mnie znaczenia w jakim języku używam tutaj):

PreparedStatement st = conn.prepareStatement("INSERT INTO status VALUES (?,default,default)"); 

for(int i = 1; i <= 100000; i++) { 
    st.setInt(1,i); 
    st.addBatch(); 
} 

System.out.println("Running batch..."); 
st.executeBatch(); 
System.out.println("Batch done!"); 

Zaczyna wkładki, ale Problem polega na tym, że wypełnienie stołu zajmuje bardzo dużo czasu (nie mam dokładnego czasu, ale trwało to wiele godzin). Moje pytanie sprowadza się do: czy istnieje łatwy i skuteczny sposób wypełniania tabeli MySQL masą takich wierszy?

+0

dodał czystego roztworu sql do mojej odpowiedzi, daj mi znać jeśli znajdziesz coś szybciej. – xception

Odpowiedz

11

ogólnie rzecz biorąc, to można użyć jednego lub więcej z następujących:

  • rozpocząć transakcję, czy wkładkami, popełnić
  • Pack, wiele wartości w jednej wkładce do zapytania
  • odrzucić wszystkie współpracy nstraints Przedtem wkładkę i przywrócenie ograniczeń po wkładce masowej (z wyjątkiem klawisza ewentualnie podstawowej, niezbyt pewny o nim chociaż)
  • Zastosowanie insert into ... select jeśli odpowiednia

Pierwszy (z wykorzystaniem transakcji) jest najbardziej prawdopodobne, aby pomóc, ale nie jestem pewien, czy działa na tabelach myisam, z innodb robi to bardzo dobrą robotę - używam tylko tych, kiedy jestem zmuszony używać mysql, wolę postgresql.

W Twoim konkretnym przypadku wkładania 100000 wierszy danych, można wykonać następujące czynności:

INSERT INTO status(id, responseCode, lastUpdate) SELECT @row := @row + 1 as row, 503, NOW() FROM 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t, 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t2, 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t3, 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t4, 
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t5, 
(SELECT @row:=0) t6; 

przetestowane na moim komputerze, otrzymała:

Query OK, 100000 rows affected (0.70 sec) 
Records: 100000 Duplicates: 0 Warnings: 0 

Jestem pewien, że można Dostajesz znacznie szybciej niż to dla 100000 wierszy.

+2

Jeśli używasz wielu instrukcji insertowych, grupowanie ich w transakcje zapobiega zapisywaniu bazy danych na dysku po każdym z nich, zapewnia to, że zostaną one zatwierdzone na dysku wszystkie na raz po zakończeniu transakcji. . –

+1

Wydaje się to dość szybkie. Czy możesz krótko wyjaśnić, co się dzieje w zapytaniu? – user17753

+0

Tworzę kolejne numery, łącząc 5 tabel zawierających od 0 do 9, a następnie wybierając opcję Liczba, stała, stała ... co jest bardzo szybkie ... następnie wstaw wszystkie 100000 wpisów w pojedynczej transakcji, ponieważ jest to pojedyncze zapytanie. – xception

1

Tworzysz jedną WIELKĄ instrukcję wsadową do wykonania. Spróbuj podzielić go na mniejsze pakiety używając np. wywołaj executeBatch() co 1000 inkrementów i (używając mod (i) yaddayadda) wewnątrz pętli. To powinno przyspieszyć proces:

for(int i = 1; i <= 100000; i++) { 
    st.setInt(1,i); 
    st.addBatch(); 
    if (mod(i,1000)=0) { 
     st.executeBatch(); 
    } 
} 
+0

Zauważyłem, że wykonanie partii (tak jak w moim pytaniu) nadal aktywnie wypełnia tabelę (np. Mogę obserwować wypełnienie db) tak samo jak twój fragment tutaj. Chociaż nie odczuwam żadnej zasadniczej różnicy w wydajności wkładek. – user17753

8

Jak o ustawienie AUTO_INCREMENT na klucz podstawowy.

Następnie wstawiając pierwsze sto (lub tysiące) wierszy w dowolny sposób (przykład lub przykład DocJones dał).

Następnie za pomocą

INSERT INTO table SELECT NULL, '503', NOW() FROM table; 

...wielokrotnie kilka razy. Powoduje to dwukrotne zwiększenie rozmiaru tabeli.

Numer NULL w pierwszym gnieździe SELECT zapewnia AUTO_INCREMENT kopnięć i przyrostów id.

Jeśli chcesz nawet Faser rosną tabelę można zrobić

INSERT INTO table SELECT NULL, '503', NOW() FROM table AS t1 CROSS JOIN table t2; 

... wielokrotnie kilka razy, co pozwoliłoby na zwiększenie wielkości stołu z potęgi dwójki poprzedniego rozmiaru + poprzedniego rozmiaru (100^2 + 100).

Pozwala także na dostosowanie wartości wstawione na przykład, jeśli chcesz utworzyć „random” responseCodes można użyć coś podobnego CONCAT(ROUND(1+RAND()*4), '0', ROUND(RAND()*5)) który daje odpowiedzi Kody od 100 do 505.

+0

Doskonałe rozwiązanie również! – DocJones

+0

Myślę, że ten pomysł jest naprawdę ciekawy. Spróbuję tego. – user17753

+1

Ostrożnie z 'CROSS JOIN' możesz wprowadzić 10 wartości ręcznie, następnie uruchomić' CROSS JOIN' i uzyskać 10 + 10^2 = 110, następnie powtórzysz 'CROSS JOIN' i puf masz 110 + 110^2 = 12,210, podczas trzeciego powtórzenia jesteś już na 149,096,310 - sto czterdzieści dziewięć ** milionów ** wpisów, które będą chowić na jakimś dysku-io - i trochę czasu, aby napisać. –

2

rozwiązanie PHP załadować je w partiach po 100:

for ($i = 0; $i < 100000; $i+=100) { 
    $vals = implode(', ', 
        array_map(function($j) { return "($j, default, default)";}, 
          range($i, $i+100))); 
    mysqli_query($dbh, 'insert into status values ' . $vals) or die mysqli_error($dbh); 
} 
Powiązane problemy