LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Przegląd bloga /IT & Networks / Wyodrębnianie pojedynczych tabel z dużej kopii SQL...
Ten artykuł w innych językach:

Wyodrębnianie pojedynczych tabel z dużej kopii SQL za pomocą PHP

Wyodrębnianie pojedynczych tabel z dużej kopii SQL za pomocą PHPWyodrębnianie pojedynczych tabel z dużej kopii SQL za pomocą PHP

Management Summary

Eksport bazy danych to plik zawierający zapisane informacje i polecenia odtworzenia tabel. Taki plik może być zbyt duży do wygodnego otwarcia, choć potrzebny jest tylko niewielki fragment. Artykuł pokazuje, jak program w PHP odczytuje dane porcjami i kopiuje wybrane tabele, umożliwiając analizę przy ograniczonej pamięci. Wyciąg ułatwia sprawdzanie zawartości, lecz może pomijać powiązane dane lub ustawienia. Nie jest więc automatycznie kompletną kopią do odtworzenia bazy.

Spis treści
  1. Dlaczego jeden przełącznik nie wystarcza
  2. Wymagany format zrzutu
  3. Przykład PHP: sekcje tabel o podanych nazwach
  4. Od fragmentu tekstu do odtworzenia
  5. Pytania i odpowiedzi
  6. Źródła i data weryfikacji

Z dużego zrzutu MySQL czasem potrzebna jest tylko konkretna tabela. Wyodrębnienie odpowiedniego tekstu nie wymaga wczytania całego pliku do edytora ani pamięci PHP. Ważne jest jednak rozróżnienie celu: fragment do analizy albo kopia bazy, którą rzeczywiście da się odtworzyć.

Filtr wierszowy sprawdza linie tekstu. Parser SQL rozpoznaje także budowę poleceń, na przykład początek i koniec wartości w cudzysłowie. Ten filtr nie wykonuje takiej analizy. Poniższy przykład PHP kopiuje wybrane sekcje tabel rozpoznawane po standardowych komentarzach. Wynik pozostaje fragmentem tekstu do sprawdzenia. Przy odtwarzaniu danych lepszym wyborem jest nowy eksport wybranych obiektów albo zweryfikowana procedura przywracania.

Fragment SQL: osobny wybór bazy i tabel

Dlaczego jeden przełącznik nie wystarcza

Filtr śledzi osobno bieżącą bazę danych i to, czy aktualna tabela ma być kopiowana. Zmiana bazy kończy bieżący wybór. Każdy nowy znacznik tabeli wymaga ponownej decyzji o kopiowaniu. Na przykład po wybranej sekcji orders niewybrana sekcja logs musi ponownie wyłączyć kopiowanie. Sprawdzanie kolejnych tabel wyłącznie przy wyłączonym przełączniku powodowałoby kopiowanie także innych tabel po pierwszym trafieniu.

Samo fgets() nie gwarantuje minimalnego zużycia pamięci. Bez ograniczenia długości odczytywany jest cały wiersz, a rozszerzony INSERT może być bardzo długi. Przykład czyta najwyżej 65 536 bajtów na krok i rozpoznaje znaczniki wyłącznie na początku wiersza. Długie wiersze danych są kopiowane porcjami.

Wymagany format zrzutu

  • Nieskompresowany plik tekstowy w typowym formacie komentarzy mysqldump, ze znacznikami baz, na przykład z eksportu przez --databases lub --all-databases.
  • Obecne są znaczniki -- Current Database: oraz odpowiednie sekcje -- Table structure for table i/lub -- Dumping data for table. Zrzuty z --skip-comments nie są obsługiwane tą metodą.
  • Dokładne nazwy bazy i tabel zamiast jednego niejednoznacznego prefiksu. Identyfikatory z osadzonym znakiem backtick i inne formaty generatorów wykraczają poza zakres przykładu.
  • PHP 8 CLI i lokalny system plików obsługujący dowiązania twarde w obrębie jednego katalogu. Dowiązanie twarde nadaje tej samej zawartości pliku kolejną nazwę; tutaj tworzy nazwę końcową bez zastępowania istniejącego pliku. Plik źródłowy, częściowy i wynikowy znajdują się poza publicznym katalogiem witryny.

Skompresowany zrzut wymaga wcześniejszego rozpakowania do chronionego katalogu z odpowiednią ilością miejsca. Obejrzenie początku pliku pomaga rozpoznać format, lecz nie potwierdza całej zawartości, wszystkich późniejszych tabel ani długości wszystkich wierszy.

Czekolada z rowkami łamie się równo na kostki, a gładka kruszy się na odłamki. Tak samo skrypt potrzebuje znaczników sekcji w zrzucie bazy danych; bez tych komentarzy podział się nie udaje.
Czekolada z rowkami łamie się równo na kostki, a gładka kruszy się na odłamki. Tak samo skrypt potrzebuje znaczników sekcji w zrzucie bazy danych; bez tych komentarzy podział się nie udaje.

Przykład PHP: sekcje tabel o podanych nazwach

Wywołanie php extract.php dump.sql excerpt.txt shop orders customers wybiera tabele orders i customers z bazy shop. Plik wynikowy i odpowiadający mu plik .part nie mogą wcześniej istnieć.

<?php
// PHP 8 CLI. Produces a text excerpt, not a validated restore file.
umask(0077);
if ($argc < 5) {
    fwrite(STDERR, "Usage: php extract.php input.sql excerpt.txt database table [table...]\n");
    exit(2);
}
[, $input, $output, $database] = $argv;
$tables = array_fill_keys(array_slice($argv, 4), true);
$part = $output . '.part';
$in = $out = false;
$created = false;

try {
    $in = @fopen($input, 'rb');
    if ($in === false) throw new RuntimeException('Cannot open input');
    $out = @fopen($part, 'xb');
    if ($out === false) throw new RuntimeException('Cannot create new partial output');
    $created = true;
    $currentDatabase = null;
    $copy = false;
    $lineStart = true;
    $seen = [];

    while (($chunk = fgets($in, 65537)) !== false) {
        if ($lineStart) {
            if (preg_match('/^-- Current Database: `([^`]+)`\r?\n?$/D', $chunk, $m)) {
                $currentDatabase = $m[1];
                $copy = false;
            } elseif (preg_match('/^-- (?:Table structure|Dumping data) for table `([^`]+)`\r?\n?$/D', $chunk, $m)) {
                $copy = $currentDatabase === $database && isset($tables[$m[1]]);
                if ($copy) $seen[$m[1]] = true;
            } elseif (str_starts_with($chunk, '-- ')) {
                $copy = false;
            }
        }
        if ($copy) {
            $offset = 0;
            $length = strlen($chunk);
            while ($offset < $length) {
                $n = fwrite($out, substr($chunk, $offset));
                if ($n === false || $n === 0) throw new RuntimeException('Write failed');
                $offset += $n;
            }
        }
        $lineStart = str_ends_with($chunk, "\n");
    }
    if (!feof($in)) throw new RuntimeException('Read failed');
    if (array_diff_key($tables, $seen)) {
        throw new RuntimeException('Requested table marker missing; format or names differ');
    }
    if (!fflush($out)) throw new RuntimeException('Flush failed');
    if (!fclose($out)) throw new RuntimeException('Close failed');
    $out = false;
    // Same-directory hard link: fails if the final name already exists.
    if (!@link($part, $output)) throw new RuntimeException('Cannot publish new output');
    if (!unlink($part)) throw new RuntimeException('Output exists, partial link cleanup failed');
    $created = false;
    fwrite(STDERR, "Text excerpt written. Separate restore validation required.\n");
} catch (Throwable $e) {
    if (is_resource($out)) fclose($out);
    if ($created && is_file($part)) unlink($part);
    fwrite(STDERR, $e->getMessage() . "\n");
    exit(1);
} finally {
    if (is_resource($in)) fclose($in);
}

Końcowy wynik pojawia się dopiero po odczytaniu wejścia, poprawnym zapisie oraz odnalezieniu przynajmniej jednego znacznika każdej wskazanej tabeli. Znacznik potwierdza wybór sekcji, a nie kompletność obiektu SQL. Istniejący wynik nie jest nadpisywany. Błędy kończą program kodem różnym od zera; częściowy fragment nie jest zgłaszany jako gotowy rezultat.

Od fragmentu tekstu do odtworzenia

Ustawienia sesji, takie jak zestaw znaków i tryb SQL, zależności kluczy obcych, triggery, widoki i procedury mogą znajdować się poza kopiowaną sekcją. Na przykład tabela zamówień może odwoływać się do identyfikatorów klientów w osobnej tabeli. Samo skopiowanie sekcji zamówień nie obejmuje rekordów tych klientów. Nietypowo zapisane ciągi SQL mogą też zawierać tekst podobny do komentarzy. Przykład nie interpretuje tych struktur i nie jest uniwersalnym filtrem importu.

Jeżeli dostępny jest serwer źródłowy, mysqldump przyjmuje nazwę bazy, a po niej konkretne nazwy tabel. Dla InnoDB opcja --single-transaction może zapewnić spójny zrzut; inne silniki i równoczesne zmiany struktury wymagają odpowiedniej strategii. Gdy pozostał wyłącznie zrzut, odtworzenie w odizolowanym środowisku i następnie eksport wybranych obiektów daje lepszą kontrolę. Przed importem potrzebna jest ocena wersji docelowej, miejsca, zależności i uprawnień.

Pytania i odpowiedzi

Jak szybko działa filtr?

Odczytywany jest cały plik wejściowy. Czas i pamięć zależą między innymi od nośnika, ilości zapisywanych danych i PHP. Uniwersalna obietnica przetwarzania wielu gigabajtów w kilka sekund nie jest wiarygodna. Ograniczony bufor zapobiega jedynie przechowywaniu całego bardzo długiego wiersza w pamięci PHP.

Co oznacza brak trafienia?

Nazwa może nie istnieć, mieć inną pisownię albo występować bez oczekiwanych komentarzy. Skrypt zgłasza wtedy błąd. Pusty fragment nie jest przedstawiany jako dowód pustej tabeli.

Czy sukces oznacza możliwość odtworzenia wszystkich wybranych obiektów?

Nie. Próba odtworzenia ze sprawdzeniem schematu, wierszy i zależności jest osobnym etapem. Oryginalny zrzut pozostaje niezmieniony.

Źródła i data weryfikacji

2026-10-24

Lukas Wójcik

Lukas Wójcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Kontakt

Masz pytanie lub pomysł na projekt internetowy? Napisz krótko, o co chodzi. Formularz korzysta z reCAPTCHA i formconfirmed do ochrony przed spamem.

Napisanie komentarza

Doświadczenia z innym sprzętem lub inną wersją oprogramowania oraz pytania o konfigurację są tu mile widziane.

Adres e-mail nie jest publikowany. Pola obowiązkowe oznaczono gwiazdką.

LUKASWOJCIK.COM · NEWSLETTER

Nowe pomysły w skrzynce

Nowe artykuły, praktyczne narzędzia i informacje o projektach.

Wybierz tematy (opcjonalnie)
Bez wyboru: wszystkie tematy.

Zapis wymaga potwierdzenia e-mailem. · Prywatność

Prywatność · Newsletter

Adres e-mail, język, wybrane tematy oraz daty zapisu i potwierdzenia są przechowywane w celu wysyłki newslettera. Wymagane jest potwierdzenie zapisu. Każda wiadomość zawiera link do natychmiastowej rezygnacji. Formconfirmed chroni formularz przed spamem. Brak śledzenia otwarć i kliknięć.

Pełna polityka prywatności ↗