我可以在MySQL的一个存储过程中放置​​多个预准备语句吗?

时间:2014-07-13 16:53:24

标签: mysql stored-procedures prepared-statement

示例代码:

BEGIN

PREPARE stmt_user_login FROM 
"SELECT COUNT(*)
FROM `t_users`
WHERE `t_users`.`username` = ? AND `t_users`.`password` = ?;";

SET @a := $username;
SET @b := $password;

EXECUTE stmt_user_login USING @a, @b;

DEALLOCATE PREPARE stmt_user_login;

IF FOUND_ROWS() = '1' THEN

PREPARE stmt_user_loggedin FROM
"INSERT INTO
`t_sessions`
(`username`, `last_request`, `last_ip`, `last_port`, `token`, `validity`)
VALUES
(?, ?, ?, ?, ?, ?);";

SET @c := $user_name;
SET @d := $last_request;
SET @e := $last_ip;
SET @f := $last_port;
SET @g := $token;
SET @h := $validity;

EXECUTE stmt_user_loggedin USING @c, @d, @e, @f, @g, @h;

DEALLOCATE PREPARE stmt_user_loggedin;

END IF;

END

2 个答案:

答案 0 :(得分:0)

是的,您可以在存储过程中使用多个预准备语句。

但这不是你手术中的问题。

FOUND_ROWS()返回上一个查询返回的行数,而不是计数。在您的过程中,行数始终为1,即使它在上一个查询中报告的计数为0。 *

相反,您应该使用SELECT ... INTO来捕获值。

此外,您不需要使用预准备语句在查询中安全地包含过程参数。你可以这样做:

BEGIN
DECLARE usercount INT;

SELECT COUNT(*) FROM t_users
WHERE t_users.username = $username AND t_users.password = $password
INTO usercount

IF usercount = 1 THEN

  INSERT INTO t_sessions (username, last_request, last_ip, last_port, token, validity)
  VALUES ($user_name, $last_request, $last_ip, $last_port, $token, $validity);

END IF;

END

我假设$username$password,其他带有$前缀的标识符是过程参数的名称。在SQL标识符上使用$前缀是不常见的,但它是合法的。

您的程序甚至可以进一步简化:

BEGIN

  INSERT INTO t_sessions (username, last_request, last_ip, last_port, token, validity)
  SELECT $user_name, $last_request, $last_ip, $last_port, $token, $validity
  FROM t.users WHERE t.users.username = $username AND t_users.password = password
  LIMIT 1;

END

这种方法的工作方式是,如果SELECT中存在匹配项,它将返回一行,其中的列由$ - 前缀变量中的值填充。然后将该行插入t_sessions。如果没有匹配,则返回零行,因此不会发生INSERT。


* 异常:FOUND_ROWS()似乎在SELECT COUNT(*)查询上返回0,如果查询使用NULL访问类型优化或“不可能在哪里”,就像使用{ {1}}。我记录了一个错误:http://bugs.mysql.com/bug.php?id=73283

还有更多,我发现针对MySQL {5.6}报告了很多其他错误,导致WHERE 1=0返回错误的结果。我不建议使用此功能。

答案 1 :(得分:0)

我最终在存储过程中使用了简单的语句,并使用PHP中的预准备语句来调用该过程。

这是我从phpMyAdmin导出的SQL PROCEDURE:

CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_login`(IN `$username` VARCHAR(32) CHARSET utf8, IN `$password` VARCHAR(32) CHARSET utf8, IN `$token` VARCHAR(32) CHARSET utf8, IN `$last_request` VARCHAR(10) CHARSET utf8)
    MODIFIES SQL DATA
BEGIN

SET @username := $username;
SET @password := $password;
SET @token := $token;
SET @last_request := $last_request;
SET @validity := '1800';

DELETE FROM `t_sessions` WHERE (`t_sessions`.`last_request` + @validity) < (@last_request + @validity)
AND `t_sessions`.`user` = @username
AND 
(SELECT COUNT(`t_users`.`user_id`)
FROM `t_users` 
WHERE `t_users`.`username` = @username 
AND `t_users`.`password` = @password) = 1;

INSERT INTO `t_sessions` (`t_sessions`.`user`, `t_sessions`.`token`, `t_sessions`.`validity`, `t_sessions`.`last_request`)
SELECT `t_users`.`username`, @token, @validity, @last_request
FROM `t_users`
WHERE `t_users`.`username` = @username
AND `t_users`.`password` = @password;

SELECT 
CASE LAST_INSERT_ID()
WHEN 0 THEN 0
ELSE 1
END AS `LOGGEDIN`;

END

和PHP部分:

include('conn.php'); // "conn.php is the file where I'm storing the connection script to mysql"
        $conn->select_db("test");
        $query_stmt = $conn->prepare("CALL `sp_login`(?, ?, ?, ?);");
        $query_stmt->bind_param("ssss", $username, $password, $token, $last_request);
        $query_stmt->execute();
        $query_stmt->bind_result($result);
        while ($query_stmt->fetch()) {
            switch ($result) {
            CASE 0:
                $query_stmt->close();
                echo 'BAD';
                break;
            CASE 1:
                $query_stmt->close();
                echo 'OK';
                break;          
            }
            if ($conn) {$conn->close();}
            exit;
        }