2011-10-31 3 views
3

Mac에서 MAMP v2.0 사용 _ _ Apache/2.0.64 (Unix) - PHP/5.3.5 - DAV/2 mod_ssl/2.0.64 - OpenSSL/0.9.7l - MySQL 5.5.9Memory Leak, PHP 및 MySQL Blob 스트리밍 파일 디버깅

나는 실행하려고하는 스크립트를 가지고 있으며 디버깅을 시도한 주요 메모리 누수를 제공하는 것으로 보입니다. 수정 방법은 해결할 수 없습니다. .

기본적으로 스크립트는 파일 관리자 모듈의 일부입니다. ID가 주어지면 파일 다운로드를 처리합니다.

전체 파일은 64KB 청크 (레코드 당)로 데이터베이스 테이블에 BLOB으로 저장되며 요청시 클라이언트로 스트리밍됩니다.

Database: file_management

Tables: file_details, file_data

file_details:
FileID - int(10) AUTO_INCREMENT
FileTypeID - int(10)
FileType - varchar(60)
FileName - varchar(255)
FileDescription - varchar(255)
FileSize - bigint(20)
FileUploadDate - datetime
FileUploadBy - int(5)

file_details:
FileDataID - int(10) AUTO_INCREMENT
FileID - int(10)
FileData - BLOB

실제로 점점 오전 오류가 (PHP 오류 로그에서)이 하나입니다

[31 10 월 2011 9시 47분 39초] PHP 치명적인 오류 : 134217728 바이트의 허용 메모리 크기 소진 이제

라인 (150)

에 /root/htdocs/file_manager/file_manager_download.php에 (63,326,173 바이트를 할당하려고), 40메가바이트보다 파일이 충분히 작은 경우이 경우, 작품을 다운로드의 실제 기능, 이하, 그러나 위의 오류에서 60mb 파일처럼 그걸 처리하면 실패합니다. 0kb 파일을 다운로드하는 것뿐입니다.

분명히 134217728 바이트는 63326173 바이트 (128MB와 60MB)를 넘습니다.

가 134,217,728 바이트의 허용 메모리 크기는 php.ini 파일에있는 지시어입니다 : "memory_limit를 = 128M, 메모리의 최대 금액은 스크립트가 소모 할 수 있습니다"나는 256M으로 설정하면

, 그것은 저에게 60mb 파일뿐만 아니라 최대 약 80mb 파일을 다운로드 할 수있게 해줍니다.

또한이 값을 1024M으로 설정하면 260MB 파일을 다운로드 할 수 있으며 더 큰 파일을 다운로드 할 수 있습니다.

그래서이 문제는 스크립트의 어딘가에서 모든 메모리를 차지하는 누출임을 알 수 있습니다. 여기

은 다운로드 스크립트입니다

 


    ini_set('display_errors',1); 
error_reporting(E_ALL & ~E_NOTICE); 

$strDB=mysql_connect("localhost","username","password")or die ("Error connecting to mysql.. Error: (" . mysql_errno() . ") " . mysql_error()); 
$database=mysql_select_db("file_management",$strDB); 

if (isset($_GET["id"])) { 

    // List of nodes representing each 64kb chunk 
    $nodelist = array(); 

    // Pull file meta-data 
    $sql_GetFileDetails = " 
    SELECT 
    FileID, 
    FileTypeID, 
    FileType, 
    FileName, 
    FileDescription, 
    FileSize, 
    FileUploadDate, 
    FileUploadBy 
    FROM file_details WHERE FileID = '".$_GET["id"]."';"; 

    $result_GetFileDetails = mysql_query($sql_GetFileDetails) or die ("No results for this FileID.
Your Query: " . $sql_GetFileDetails . "
Error: (" . mysql_errno() . ") " . mysql_error()); if (mysql_num_rows($result_GetFileDetails) != 1) { die ("A MySQL error has occurred.
Your Query: " . $sql_GetFileDetails . "
Error: (" . mysql_errno() . ") " . mysql_error()); } // Set the file object to get details from $FileDetailsArray = mysql_fetch_assoc($result_GetFileDetails); // Pull the list of file inodes $sql_GetFileDataNodeIDs = "SELECT FileDataID FROM file_data WHERE FileID = ".$_GET["id"]." order by FileDataID"; if (!$result_GetFileDataNodeIDs = mysql_query($sql_GetFileDataNodeIDs)) { die("Failure to retrive list of file inodes
Your Query: " . $sql_GetFileDataNodeIDs . "
Error: (" . mysql_errno() . ") " . mysql_error()); } while ($row_GetFileDataNodeIDs = mysql_fetch_assoc($result_GetFileDataNodeIDs)) { $nodelist[] = $row_GetFileDataNodeIDs["FileDataID"]; } $FileExtension = explode(".",$FileDetailsArray["FileName"]); $FileExtension = strtolower($FileExtension[1]); // Determine Content Type switch ($FileExtension) { case "mp3": $ctype="audio/mp3"; break; case "wav": $ctype="audio/wav"; break; case "pdf": $ctype="application/pdf"; break; //case "exe": $ctype="application/octet-stream"; break; case "zip": $ctype="application/zip"; break; case "doc": $ctype="application/msword"; break; case "xls": $ctype="application/vnd.ms-excel"; break; case "ppt": $ctype="application/vnd.ms-powerpoint"; break; case "gif": $ctype="application/force-download"; break; // This forces download, instead of viewing in browser. case "png": $ctype="application/force-download"; break; // This forces download, instead of viewing in browser. case "jpeg": $ctype="application/force-download"; break; // This forces download, instead of viewing in browser. case "jpg": $ctype="application/force-download"; break; // This forces download, instead of viewing in browser. default: $ctype="application/force-download"; // This forces download, instead of viewing in browser. } // Send down the header to the client header("Date: ".gmdate("D, j M Y H:i:s e", time())); header("Cache-Control: max-age=2592000"); //header("Last-Modified: ".gmdate("D, j M Y H:i:s e", $info['mtime'])); //header("Etag: ".sprintf("\"%x-%x-%x\"", $info['ino'], $info['size'], $info['mtime'])); header("Accept-Ranges: bytes"); //header("Cache-Control: Expires ".gmdate("D, j M Y H:i:s e", $info['mtime']+2592000)); header("Pragma: public"); // required header("Expires: 0"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Cache-Control: private",false); // required for certain browsers header("Content-Description: File Transfer"); header("Content-Disposition: attachment; filename=\"".$FileDetailsArray["FileName"]."\""); header("Content-Transfer-Encoding: binary"); header("Content-Type: ".$FileDetailsArray["FileSize"]); ob_end_clean(); ob_start(); ob_start("ob_gzhandler"); $sql_GetFileDataBlobs = "SELECT FileData FROM file_data WHERE FileID = ".$_GET["id"]." ORDER BY FileDataID ASC;"; if (!$result_GetFileDataBlobs = mysql_query($sql_GetFileDataBlobs)) { die("Failure to retrive list of file inodes
Your Query: " . $sql_GetFileDataBlobs . "
Error: (" . mysql_errno() . ") " . mysql_error()); } while ($row_GetFileDataBlobs = mysql_fetch_array($result_GetFileDataBlobs)) { echo $row_GetFileDataBlobs["FileData"]; } ob_end_flush(); header('Content-Length: '.ob_get_length()); ob_end_flush(); }

나는 Xdebug는 출력 최대 메모리 사용의 결과를 사용했다, 그러나 아무것도 총 피크 메모리 사용량에 제한 근처 어디서든 뭔가를 한 것 할 나타납니다 900kb 페이지.

그래서 나는 파일 덩어리를 메모리에 모으고 그것들을 보내지 않을 것이라고 생각하고있다. 그러나 파일 덩어리 만이 그 양의 메모리에 도달하여 스크립트가 실패하게 만든다.

당신이 좋아하는 경우에 당신이 내 스크립트를 테스트 할 수 있도록 데이터베이스에 파일을 업로드하는 스크립트를 제공 할 수 있습니다, 그냥 날 어떤 도움

건배를 알려주세요!


* /////////이 해결 ///////// *

난 그냥 감사 hafichuk을 말하고 싶은

, 큰 호응 내 모든 문제를 해결했습니다.

문제는 두 가지로 나뉩니다.

1 - while 루프에서 ob_flush()를 사용하지 않았습니다. 나는 그것을 추가하고 더 많은 다운로드를 가능하게하는 많은 메모리를 확보 한 것으로 보였지만 무제한은 아니었다.

예를 들어, memory_limit = 128M을 사용하면 이제 40MB 이상을 다운로드 할 수 있습니다. 실제로는 약 200MB까지 사용할 수 있습니다. 그러나 이것이 다시 실패한 곳입니다. 첫 번째 메모리 문제가 해결되었습니다.

수업 1 : 개체를 플러시하십시오!

2 - mysql_query를 사용하여 SQL 쿼리에 대한 결과를 검색하고있었습니다. 문제는이 결과를 버퍼링하고 이것이 메모리 한계 문제에 추가된다는 것입니다.

대신 mysql_unbuffered_query를 사용하여 결과적으로 완벽하게 작동합니다.

그러나 이는 결과를 읽는 동안 테이블을 잠그는 몇 가지 제한 사항이 있습니다.

2 단계 : 필요없는 경우 mysql 결과를 버퍼링하지 마십시오!

최종 LESSON (프로그램 제한 내에서) :

는 이러한 수정의 모든 작업

, 그러나, 이들의 조합에 문제가 없음을 확인하기 위해 몇 가지 더 많은 테스트가 필요합니다.

또한 객체와 PHP 메모리 할당에 대해 많은 것을 배웠습니다. xdebug가 제공하는 것보다 약간 더 시각적으로 프로세스를 시각적으로 디버깅 할 수있는 방법이 있었으면합니다. 누군가 xdebug가 실제로이 과정에서 어떤 빛을 발할 수 있었는지에 대한 아이디어가 있으면 의견에 알려주십시오.

앞으로 도움이되기를 바랍니다.

건배

+0

while 루프에서 http://php.net/manual/en/function.ob-flush.php를 사용해 보셨나요? – hafichuk

+0

당신은 전설입니다! 그것은 (거의) 완벽하게 작동합니다. 그냥 알려주면 memory_limit 지시문을 완전히 사용할 수있는 것처럼 보입니다. 따라서 128M로 설정하고 140MB 파일을 시도하면 작동하지만 250MB 파일을 다운로드하려고하면 약 200MB가 스트리밍되어 PHP 오류가 발생합니다 : [31-Oct-2011 11:46:01] PHP 치명적 오류 :/root/htdocs/file_manager/file_manager_download에서 134217728 바이트의 메모리 크기가 소모되었습니다 (132392961 바이트를 할당하려고했습니다). 125 번째 라인의 .php는 ob_flush(); 선. – Quantico773

+0

mysql_unbuffered_query 내 하루 저장되었습니다, 감사합니다. – webtweakers

답변

1

당신은 당신의 while 루프에서 "() ob_flush"을해야한다. 그러면 페이지의 버퍼가 지워집니다. 데이터가 시작된 후에는 헤더를 보낼 수 없으므로 콘텐츠 길이가 나열된 마지막 헤더는 제거해야합니다. 이 파일을 다운로드 할 때 문제가되어서는 안되며 다운로드 진행률 측정기 만 업데이트하면됩니다.

+0

안녕하세요. hafichuk, while 루프에이 줄을 추가했습니다. 위의 주석에서 대답 했으므로 memory_limit 지시문의 제한에 대해서만 작동하는 것처럼 보입니다. 500MB라고 말할 수있는 파일을 다운로드하려면 php.ini에서 memory_limit 지시문을 늘려야합니까? 또는 지시문과 상관없이 모든 파일 크기를 처리 할 수있는 ob_flush이지만 다른 곳에서 코드가 올바르지 않습니까? 당신의 도움에 많은 감사드립니다. – Quantico773

+0

mysql_fetch_X는 데이터를 버퍼링하므로 http://www.php.net/manual/en/function.mysql-unbuffered-query.php를 사용하여보아야 할 수도 있습니다. – hafichuk

+0

데이터베이스 외부에서 파일을 저장하고 http://www.php.net/manual/en/function.fpassthru.php를 사용하는 것이 좋을 수도 있습니다. – hafichuk