LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Przegląd bloga /IT & Networks / Ekstrakcja konkretnych fragmentów z ogromnych zrzutów SQL...
Ten artykuł w innych językach:

Ekstrakcja konkretnych fragmentów z ogromnych zrzutów SQL przy użyciu PHP

Ekstrakcja konkretnych fragmentów z ogromnych zrzutów SQL przy użyciu PHP
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 nie jest parserem SQL. 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

Baza i tabela to dwa oddzielne stany. Zmiana bazy kończy bieżący wybór. Każdy nowy znacznik tabeli wymaga ponownej decyzji o kopiowaniu. 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. 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.

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ą. 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 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ą.