En iyi uygulama: PHP İthalat mySQL dosyası;

11 Cevap php

Ben bir paylaşılan barındırma sağlayıcı bir web sitesini güncellemek gereken bir durum var. Site CMS vardır. CMS dosyalarını yükleyerek FTP kullanarak oldukça basittir.

Ben de bir (bir PHP script sınırları göre) büyük veritabanı dosyasını (Çevresi 2-3 MB sıkıştırılmamış) almanız gerekir. Mysql dışarıdan erişime kapalı, bu yüzden FTP kullanarak bir dosyayı yüklemek zorunda ve onu almak için bir PHP komut dosyası başlar. Ben yerli PHP kullanarak bunu ayrıştırmak ve sorgulamak zorunda ne yazık ki, ben mysql komut satırı işlevine erişimi yok. Ben de LOAD DATA INFILE kullanamazsınız. Ben de phpMyAdmin gibi interaktif ön uç her türlü kullanamazsınız, bu otomatik bir şekilde çalıştırmak gerekiyor. Ayrıca kullanamaz mysqli_multi_query().

Herkes biliyor ya reliably tek sorguları içine böyle bir dosya böler (multi-line ifadeler olabilir) ve sorgu çalıştıran bir zaten kodlu, basit bir çözüm var mı. Böylece ve, not alanları satır sonları ile nasıl başa, ben nedeniyle bir alan ayırıcı verilerin bir parçası olup olmadığını tespit etmek için nasıl (rastlamak olasıdır olduğum birçok FRİKİKLERİNDEN için onunla kendimi işe yaramaz başlamak önlemek istiyorum ) üzerinde. must Bunun için hazır bir çözüm bulunmalıdır.

EDIT: Thanks for your input. I left a comment to most answers explaining which answer I accepted and why.

11 Cevap

İşte gerekir bir hafıza dostu fonksiyonudur split a big file in individual queries without needing to open the whole file at once:

function SplitSQL($file, $delimiter = ';')
{
    set_time_limit(0);

    if (is_file($file) === true)
    {
        $file = fopen($file, 'r');

        if (is_resource($file) === true)
        {
            $query = array();

            while (feof($file) === false)
            {
                $query[] = fgets($file);

                if (preg_match('~' . preg_quote($delimiter, '~') . '\s*$~iS', end($query)) === 1)
                {
                    $query = trim(implode('', $query));

                    if (mysql_query($query) === false)
                    {
                        echo '<h3>ERROR: ' . $query . '</h3>' . "\n";
                    }

                    else
                    {
                        echo '<h3>SUCCESS: ' . $query . '</h3>' . "\n";
                    }

                    while (ob_get_level() > 0)
                    {
                        ob_end_flush();
                    }

                    flush();
                }

                if (is_string($query) === true)
                {
                    $query = array();
                }
            }

            return fclose($file);
        }
    }

    return false;
}

Ben büyük bir phpMyAdmin SQL dökümü üzerinde test edilmiş ve gayet güzel çalıştı.


Some test data:

CREATE TABLE IF NOT EXISTS "test" (
    "id" INTEGER PRIMARY KEY AUTOINCREMENT,
    "name" TEXT,
    "description" TEXT
);

BEGIN;
    INSERT INTO "test" ("name", "description")
    VALUES (";;;", "something for you mind; body; soul");
COMMIT;

UPDATE "test"
    SET "name" = "; "
    WHERE "id" = 1;

And the respective output:

SUCCESS: CREATE TABLE IF NOT EXISTS "test" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT, "description" TEXT );
SUCCESS: BEGIN;
SUCCESS: INSERT INTO "test" ("name", "description") VALUES (";;;", "something for you mind; body; soul");
SUCCESS: COMMIT;
SUCCESS: UPDATE "test" SET "name" = "; " WHERE "id" = 1;

StackOverflow XML formatında aylık veri dökümü serbest zaman, ben bir MySQL veritabanı içine yüklemek için PHP betik yazdı. Ben birkaç dakika içinde XML yaklaşık 2.2 gigabayt ithal.

Benim teknik sütun değerleri için parametre yer tutucuları ile prepare() an INSERT deyimi etmektir. Sonra Parametrelerin takarak, XMLReader to loop over the XML elements and execute() Benim hazırlanmış sorgu kullanın. Bir akış XML okuyucusu çünkü ben XMLReader seçtim; yerine belleğe tüm dosyayı yüklemek için gerektiren aşamalı XML girişi okur.

Ayrıca bir CSV fgetcsv() ile bir defada bir satır dosya okuyabilir.

Eğer InnoDB tabloları içine inporting ediyorsanız, ben AutoCommit yükünü azaltmak için, açıkça işlemleri başlamadan ve işlemekle öneririz. Ben her 1000 satır işlemek, ancak bu keyfidir.

Ben (çünkü StackOverflow lisanslama politikasının) burada koduna gitmiyorum, ama pseudocode:

connect to database
open data file
PREPARE parameterizes INSERT statement
begin first transaction
loop, reading lines from data file: {
    parse line into individual fields
    EXECUTE prepared query, passing data fields as parameters
    if ++counter % 1000 == 0,
        commit transaction and begin new transaction
}
commit final transaction

PHP bu kodu yazma roket bilimi değildir, ve bir hazır deyimleri ve açık hareketleri kullanır oldukça hızlı çalışır. Bu özellikler eski bulunmayan mysql PHP extension, but you can use them if you use mysqli veya PDO_MySQL.

Veri dosyası alanlarından birini içermez zaman ben de hata kontrolü, raporlama ilerleme ve varsayılan değerler için destek gibi uygun şeyler ekledi.

Ben yüklemek için gereken her tablo için alt sınıf bir abstract PHP sınıfı kodumu yazdı. Her alt sınıf, yüklemek istediği sütunları bildirir ve (veri dosyası CSV ise ya pozisyonu) adıyla XML veri dosyasındaki alanların onları eşler.

Eğer phpMyAdmin yükleyemezsiniz, dosyayı gziplemek (çok daha küçük yapmak gerekir hangi) ve phpMyAdmin kullanarak içe?

Eğer phpMyAdmin kullanmak değil, eğer EDIT: Eh, phpMyAdmin gelen kodu kullanabilirsiniz. Ben bu özel bölümü hakkında emin değilim, ama generaly güzel yapılandırılmış oluyor.

Export

The first step is getting the input in a sane format for parsing when you export it. From your question it appears that you have control over the exporting of this data, but not the importing.

~: mysqldump test --opt --skip-extended-insert | grep -v '^--' | grep . > test.sql

This dumps the test database excluding all comment lines and blank lines into test.sql. It also disables extended inserts, meaning there is one INSERT statement per line. This will help limit the memory usage during the import, but at a cost of import speed.

Import

Ithalat komut bu kadar basittir:

<?php

$mysqli = new mysqli('localhost', 'hobodave', 'p4ssw3rd', 'test');
$handle = fopen('test.sql', 'rb');
if ($handle) {
    while (!feof($handle)) {
        // This assumes you don't have a row that is > 1MB (1000000)
        // which is unlikely given the size of your DB
        // Note that it has a DIRECT effect on your scripts memory
        // usage.
        $buffer = stream_get_line($handle, 1000000, ";\n");
        $mysqli->query($buffer);
    }
}
echo "Peak MB: ",memory_get_peak_usage(true)/1024/1024;

Aşağıda gösterildiği gibi, bu hafıza bir saçma düşük miktarda kullanmak olacaktır:

daves-macbookpro:~ hobodave$ du -hs test.sql 
 15M    test.sql
daves-macbookpro:~ hobodave$ time php import.php 
Peak MB: 1.75
real    2m55.619s
user    0m4.998s
sys 0m4.588s

Ne söylüyor sen sadece 3 dakika içinde 1.75 MB pik RAM kullanımı ile bir 15MB mysqldump işlenmiş olduğunu.

Alternate Export

Eğer yeterince yüksek bir memory_limit var ve bu çok yavaş ise, aşağıdaki ihracat kullanarak deneyebilirsiniz:

~: mysqldump test --opt | grep -v '^--' | grep . > test.sql

Bu tek bir sorguda birden fazla satır eklemek genişletilmiş ekler, sağlayacaktır. Burada aynı datbase için istatistikleri:

daves-macbookpro:~ hobodave$ du -hs test.sql 
 11M    test.sql
daves-macbookpro:~ hobodave$ time php import.php 
Peak MB: 3.75
real    0m23.878s
user    0m0.110s
sys 0m0.101s

O 3.75 MB de RAM 2x üzerinde kullanır, ama sürece yaklaşık 1/6th sürer dikkat edin. Ben sizin ihtiyaçlarınıza uygun yöntemleri ve görme hem de denemenizi öneririz.

Edit:

Ben CHAR, VARCHAR, İKİLİ, VARBINARY ve BLOB alan türlerinden herhangi birini kullanarak herhangi mysqldump çıkışı anlamıyla görünmesini satırbaşı alamadı. Eğer olsa BLOB / BINARY alanlarını var ise o durumda sadece aşağıdaki kullanın lütfen:

~: mysqldump5 test --hex-blob --opt | grep -v '^--' | grep . > test.sql

Kullanabilirsiniz LOAD DATA INFILE?

SELECT INTO Dosyası kullanarak db döküm dosyası biçimlendirmek istiyorsanız, bu tam olarak neye ihtiyacınız olmalıdır. PHP için hiçbir sebep şey ayrıştırmak.

Ben aynı sorun koştu. Ben normal bir ifade kullanarak bunu çözdü:

function splitQueryText($query) {
    // the regex needs a trailing semicolon
    $query = trim($query);

    if (substr($query, -1) != ";")
        $query .= ";";

    // i spent 3 days figuring out this line
    preg_match_all("/(?>[^;']|(''|(?>'([^']|\\')*[^\\\]')))+;/ixU", $query, $matches, PREG_SET_ORDER);

    $querySplit = "";

    foreach ($matches as $match) {
        // get rid of the trailing semicolon
        $querySplit[] = substr($match[0], 0, -1);
    }

    return $querySplit;
}

$queryList = splitQueryText($inputText);

foreach ($queryList as $query) {
    $result = mysql_query($query);
}

Bir sorgu Yarma güvenilir ayrıştırma olmadan yapılamaz. Burada normal bir ifade ile doğru bölmek imkansız olurdu geçerli SQL olduğunu.

SELECT ";"; SELECT ";\"; a;";
SELECT ";
    abc";

Ben bir sorgu dizgeciklerini içeren PHP küçük SqlFormatter sınıf yazdım. Ben güvenilir (yukarıdaki örnekte de dahil olmak üzere) tüm sorguları böler bunun bir splitQuery yöntem ekledi.

https://github.com/jdorn/sql-formatter/blob/master/SqlFormatter.php

Sen biçimini kaldırmak ve onlara ihtiyacınız yoksa yöntemleri vurgulayabilirsiniz.

Tek dezavantajı bu büyük sql dosyaları ile çalışıyorsanız, bir sorun olabilir, bellekte olmak üzere bütün sql dize gerektirir. Ben müdahalesi biraz ile, bunun için bir dosya tanıtıcısı üzerinde getNextToken yöntem işe yapmak eminim.

First at all thanks for this topic. This saved a lot of time for me :) And let me to make little fix for your code. Sometimes if TRIGGERS or PROCEDURES is in dump file, it is not enough to examine the ; delimiters. In this case may be DELIMITER [something] in sql code, to say that the statement will not end with ; but [something]. For example a section in xxx.sql:

    DELIMITER //
    CREATE TRIGGER `mytrigger` BEFORE INSERT ON `mytable`
    FOR EACH ROW BEGIN
         SET NEW.`create_time` = NOW();
    END
    //
    DELIMITER ;

So first need to have a falg, to detect, that query does not ends with ; And delete the unqanted query chunks, because the mysql_query does not need delimiter (the delimiter is the end of string) so mysql_query need someting like this:

    CREATE TRIGGER `mytrigger` BEFORE INSERT ON `mytable`
    FOR EACH ROW BEGIN
         SET NEW.`create_time` = NOW();
    END;

Yani burada küçük bir çalışma ve sabit kodu:

    function SplitSQL($file, $delimiter = ';')
    {
        set_time_limit(0);            
        $matches = array();
        $otherDelimiter = false;
        if (is_file($file) === true) {
            $file = fopen($file, 'r');
            if (is_resource($file) === true) {
                $query = array();
                while (feof($file) === false) {
                    $query[] = fgets($file);
                    if (preg_match('~' . preg_quote('delimiter', '~') . '\s*([^\s]+)$~iS', end($query), $matches) === 1){     
                        //DELIMITER DIRECTIVE DETECTED
                        array_pop($query); //WE DON'T NEED THIS LINE IN SQL QUERY
                        if( $otherDelimiter = ( $matches[1] != $delimiter )){
                        }else{
                            //THIS IS THE DEFAULT DELIMITER, DELETE THE LINE BEFORE THE LAST (THAT SHOULD BE THE NOT DEFAULT DELIMITER) AND WE SHOULD CLOSE THE STATEMENT                                
                            array_pop($query);
                            $query[]=$delimiter;
                        }                                                                                    
                    }                        
                    if ( !$otherDelimiter && preg_match('~' . preg_quote($delimiter, '~') . '\s*$~iS', end($query)) === 1) {                            
                        $query = trim(implode('', $query));
                        if (mysql_query($query) === false){
                            echo '<h3>ERROR: ' . $query . '</h3>' . "\n";
                        }else{
                            echo '<h3>SUCCESS: ' . $query . '</h3>' . "\n";
                        }
                        while (ob_get_level() > 0){
                            ob_end_flush();
                        }
                        flush();                        
                    }
                    if (is_string($query) === true) {
                        $query = array();
                    }
                }                    
                return fclose($file);
            }
        }
        return false;
}

I hope i could help somebody too. Have a nice day!

http://www.ozerov.de/bigdump/ 200 + MB sql dosyasını içe benim için çok faydalı oldu.

Note: SQL file should be already present in the server so that the process can be completed without any issue

Dosyayı alma için phpMyAdmin kullanabilirsiniz. Bu çok büyük olsa bile, sadece kullanmak UploadDir yapılandırma dizin, oraya upload ve phpMyAdmin ithalat sayfasından seçin. Dosya işleme PHP sınırlarına yakın olacak sonra, phpMyAdmin, ithal kesmeleri tekrar ithalat devam etmek gösteren önceden tanımlanmış değerleri ile sayfa almak gösterir.

hakkında ne düşünüyorsunuz:

system("cat xxx.sql | mysql -l username database");