PHP,PDO和SQLSRV在一个INSERT语句上执行多次

时间:2012-02-14 15:59:04

标签: php sql-server pdo

我已经在MySQL和Apache服务器上使用PDO和PHP一段时间了。我最近的任务是将业务的遗留Web应用程序转换为新的设置。旧的设置是标准的Linux Web堆栈(Apache / PHP / MySQL / Filezilla),新的设置将是带有IIS / PHP(快速cgi安装)/ SQL Server 2003 /无FTP的Windows服务器2003。

除了转换MySQL语句以更新具有文件访问信息的表之外,我几乎所有工作都在工作。将PDO与SQLSRV驱动程序一起使用,并在“文件下载”中执行插入语句。 PHP脚本,将多个记录插入SQL表中。

download.php会向SQL服务器发出多个查询。一次检查一个表中的文件是否存在和变量,然后THEN用访问信息更新另一个表。

  

请参阅下面的download.php代码

调试将$ count的print / echo显示为1.但是,检查SQL Server记录时,始终显示多个已插入的记录。有时它只是一个额外的行,总共两个,但有时它插入的高达四个EXTRA语句。在每种情况下,$ count仍显示为1。

此特定PHP脚本在调用此insert语句之前验证SQL数据库的信息。首先,验证文件访问的验证(成功),验证文件是否存在(成功),然后用下载信息(ERROR)更新访问表,最后将PDF提供给用户(成功)。

当我手动向查询分析器发出INSERT语句时,它会成功并按预期工作;它每次都插入一行。该错误似乎与执行()的SQLSRV或PDO实现有关。

我已经在stackoverflow,serverfault和全能的Google上搜索了相关信息。返回的唯一类型的结果是用户需要在一个语句/执行中执行多个查询/插入。我的问题恰恰相反;我希望只执行一个插入语句,但总是会执行多个插入语句。

问题是:为什么会发生这种情况?如何防止多次插入?

每次请求更新

访问此文件的代码是来自另一个网页的单个链接。该页面列出了允许用户访问的当前文件,并提供了download.php脚本的链接,用于验证,更新和实际提供PDF。

查看页面有一个链接列表(打印在for循环中),排列如下:

<a href='download.php?f={$item['name']}&t={$type}' target='_blank'>{$item['name']}</a>

当用户点击该链接时,除了上面的download.php的其他代码之外,下面的脚本也会运行。它成功地提供PDF文件。 download.php将内容作为PHP标题/内联PDF发送:

  

见下面的代码

查看服务器日志会向download.php文件显示两个 GET请求:

2012-02-14 17:44:37 W3SVC1785071458 172.17.31.254 GET /download.php f=06304844-1A.pdf&t=av 4090 - 172.17.31.112 Mozilla/5.0+(Windows+NT+6.1)+AppleWebKit/535.7+(KHTML,+like+Gecko)+Chrome/16.0.912.77+Safari/535.7 200 0 0
2012-02-14 17:44:37 W3SVC1785071458 172.17.31.254 GET /download.php f=06304844-1A.pdf&t=av 4090 - 172.17.31.112 Mozilla/5.0+(Windows+NT+6.1)+AppleWebKit/535.7+(KHTML,+like+Gecko)+Chrome/16.0.912.77+Safari/535.7 200 0 0

我已经在Firefox,Opera和IE(6-9b)中测试过,结果是一样的。

更新两次

将整个download.php文件放在这里:

<?php
session_start();
require("cgi-bin/auth.php");

// Don't timeout when downloading large files
@ignore_user_abort();  
@set_time_limit(0);  

//error_reporting(E_ALL); 
//ini_set('display_errors',1);

function getfile() {
    require('cgi-bin/connect_db_pdf.php');
    //Verify information 
    if (!isset($_GET) || !isset($_GET['f']) || !isset($_GET['t'])) {
        echo "Nothing to do!";
        exit(0);

    }

    //Update variables
    $vuname = strtolower(trim($_SESSION['uname']));
    $file = trim($_GET['f']); //Filename we're looking for
    $type = trim($_GET['t']);//Filetype

    if (!preg_match('/^[a-zA-Z0-9_\-\.]{1,60}$/', $file) || !preg_match('/^av|ds|cr|dp$/', $type)) {
        echo "Non conforming values";
        exit(0);
    }

    try {

        $sQuery = "SELECT * FROM pdf_info WHERE PDF_name=:file AND PDF_type=:type";

        $statm = $conn->prepare($sQuery);
        $statm->execute(array(':file'=>$file,':type'=>$type));
        $result = $statm->fetch();
        $count = $statm->rowCount();
        $sQuery = null;
        $statm = null;

        if ($count == 1 ){ //File was found in the database so let them download it. Update the time as well

            $sQuery = "INSERT INTO access (PDF_name,PDF_type, PDF_time, PDF_access) VALUES (:file, :type, GetDate(), :vuname)";

            $statm = $conn->prepare($sQuery);
            $statm->execute(array( ':vuname'=>$vuname, ':file'=>$file, ':type'=>$type));
            $count = $statm->rowCount();
            $sQuery = null;
            $statm = null;

            $sQuery = "UPDATE pdf_info SET last_view=GetDate(),viewed_uname=:vuname WHERE PDF_name=:file AND PDF_type=:type";

            $statm = $conn->prepare($sQuery);
            $statm->execute(array( ':vuname'=>$vuname, ':file'=>$file, ':type'=>$type));
            $sQuery = null;
            $statm = null;

            //$result is from FIRST SELECT query outside this 'if' scope.
            $file_loc = $result['floc'];
            $file_name = $result['filename'];

            $fileh = fopen($file_loc,'rb');//Send content to browser as inline PDF
            header("Content-Type: application/pdf"); 
            header("Pragma: no-cache");  
            header("Cache-Control: must-revalidate, post-check=0, pre-check=0");  
            header("Content-Length: " . filesize($file_loc));  
            header("Accept-Ranges: bytes");
            header("Content-Disposition: inline; filename={$file_name}");  

            while (!feof($fileh)) { 
                echo(@fgets($fileh, 8192)); 
            } 

            fclose ($fileh); 
            exit(0);

            } else { //We did not find a file in the database. Redirect the user to the view page.
                header("Location: view.php");
            }

            }   catch(PDOException $err) {//PDO SQL error. 
            //echo $err;
            header('Location: error.php');
            exit(0);
         }
}

getfile();

?>

3 个答案:

答案 0 :(得分:1)

您评论下载脚本被调用两次,如果找不到正确的值,则应退出。这可能不是这种情况,因为您滥用PDOStatement的rowCount()来测试找到的文件。此方法旨在返回DELETE,UPDATE或INSERT语句的受影响行数,但不是SELECT

  

如果关联的PDOStatement执行的最后一条SQL语句是SELECT语句,则某些数据库可能会返回该语句返回的行数。 但是,并不保证所有数据库都存在此行为,并且不应依赖于便携式应用程序。    http://php.net/manual/en/pdostatement.rowcount.php

他们继续:

  

对于大多数数据库,PDOStatement :: rowCount()不返回受SELECT语句影响的行数。相反,使用PDO :: query()发出SELECT COUNT(*)语句,其语句与预期的SELECT语句相同,然后使用PDOStatement :: fetchColumn()来检索将返回的行数。

如果您调用download.php脚本两次 - 一次使用不正确的文件值(恰好通过正则表达式测试)和一次使用正确的值,即使您只是下载,也很可能会插入两次一次。

答案 1 :(得分:1)

如果您使用Apache的mod_rewrite页面实际上已加载两次。例如,在索引页面上使用以下代码:

<?php

  session_start();

  if (!isset($_SESSION['id']))
  {
    $_SESSION['id'] = 1;
  }

  else
  {
    $_SESSION['id'] += 1;
  }

  echo $_SESSION['id'];

?>

不使用重定向,每次刷新时输出增加1。现在添加一个.htaccess文件,其中包含以下内容:

<IfModule mod_rewrite.c>
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ index.php
</IfModule>

每次刷新都会使页面增加2。我使用单个INSERT查询尝试了相同的操作,同样的情况发生:使用.htaccess,插入两行;没有,插入一行。

因此,如果您使用mod_rewrite并且它会影响download.php,那么我认为这是问题所在。

答案 2 :(得分:1)

提示:从所有PHP文件的末尾删除?>。偶尔你最终会得到一个不可见的空格或换行符,当输出像PDF这样的二进制格式时,它就会成为PDF的一部分而使它变得腐败。 (因此浏览器/ PDF阅读器挂起。)