【问题标题】:In MySQL, should I quote numbers or not?在 MySQL 中,我应该引用数字吗?
【发布时间】:2011-10-10 13:14:45
【问题描述】:

例如 - 我从 cli 创建数据库和表并插入一些数据:

CREATE DATABASE testdb CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
USE testdb;
CREATE TABLE test (id INT, str VARCHAR(100)) TYPE=innodb CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
INSERT INTO test VALUES (9, 'some string');

现在我可以做到这一点,并且这些示例确实有效(所以 - 引号不会影响任何看起来的东西):

SELECT * FROM test WHERE id = '9';
INSERT INTO test VALUES ('11', 'some string');

所以 - 在这些示例中,我通过 string 选择了一行,该行实际上存储为 mysql 中的 INT,然后我在 INT 列中插入了一个 string .

我不太明白为什么它在这里的工作方式。为什么允许在INT列中插入字符串?

我可以将所有 MySQL 数据类型作为字符串插入吗?

这种行为标准是否跨不同的 RDBMS?

【问题讨论】:

    标签: mysql sql ansi-sql


    【解决方案1】:

    MySQL 很像 PHP,并且会尽可能地自动转换数据类型。由于您使用的是 int 字段(左侧),因此它也会尝试将参数的右侧透明地转换为 int,因此 '9' 将变为 9

    严格来说,引号是不必要的,并且会强制 MySQL 进行类型转换/转换,因此会浪费一些 CPU 时间。实际上,除非您运行的是 Google 规模的操作,否则这种转换开销将非常小。

    【讨论】:

    • 我认为引号也可能导致由于强制转换而未使用索引的情况。所以我也建议正确地做,不要在数字上使用引号。这也将使向更符合标准的 DBMS 的过渡变得更加容易。
    • 我想我的主要问题是:当我从 PHP 进行插入时 - 默认情况下会引用所有数字。看起来我必须使用 PDO::PARAM_INT 来取消引用数字。我只是不明白为什么网上有这么多示例忽略了这些真正需要的 PDO::PARAM_ 常量。
    • PDO 虽然是普遍推荐的解决方案,但在绑定参数和类型转换方面却是一堆废话。
    • @marc B:你有什么推荐的? mysqli?嗯。我不记得有任何其他选择。 Mysql C api 为每个 mysql 类型定义代码:dev.mysql.com/doc/refman/5.5/en/…,但是 mysqli 或 PDO 只给你 4-5 类型绑定。 mysqli: (i,f,s,d) 和 PDO: (PDO::PARAM_INT, PDO::PARAM_STR 等)
    • 所以我想没有引号会更快?
    【解决方案2】:

    你不应该在数字周围加上引号。这是有正当理由的。

    真正的问题归结为类型转换。当您将数字放在引号内时,它被视为字符串,MySQL 必须将其转换为数字才能执行查询。虽然这可能需要一小段时间,但真正的问题开始出现在 MySQL 不能很好地转换字符串时。例如,MySQL 会将基本字符串(如 '123')转换为整数 123,但会将一些较大的数字(如 '18015376320243459')转换为浮点数。由于浮点可以四舍五入,因此您的查询可能会返回不一致的结果。 Learn more about type casting here。根据您的服务器硬件和软件,这些结果会有所不同。 MySQL 解释了这一点。

    如果您担心 SQL 注入,请始终先检查值并使用 PHP 去除任何非数字。您可以为此使用 preg_replace:preg_replace("/[^0-9]/", "", $string)

    此外,如果您使用引号编写 SQL 查询,它们将不适用于 PostgreSQL 或 Oracle 等数据库。

    【讨论】:

      【解决方案3】:

      检查这个,你会更好地理解...

      mysql> EXPLAIN SELECT COUNT(1) FROM test_no WHERE varchar_num=0000194701461220130201115347;
      +----+-------------+------------------------+-------+-------------------+-------------------+---------+------+---------+--------------------------+
      | id | select_type | table                  | type  | possible_keys     | key                  | key_len | ref  | rows    | Extra                    |
      +----+-------------+------------------------+-------+-------------------+-------------------+---------+------+---------+--------------------------+
      |  1 | SIMPLE      | test_no | index | Uniq_idx_varchar_num | Uniq_idx_varchar_num | 63      | NULL | 3126240 | Using where; Using index |
      +----+-------------+------------------------+-------+-------------------+-------------------+---------+------+---------+--------------------------+
      1 row in set (0.00 sec)
      
      mysql> EXPLAIN SELECT COUNT(1) FROM test_no WHERE varchar_num='0000194701461220130201115347';
      +----+-------------+------------------------+-------+-------------------+-------------------+---------+-------+------+-------------+
      | id | select_type | table                  | type  | possible_keys     | key               | key_len | ref   | rows | Extra       |
      +----+-------------+------------------------+-------+-------------------+-------------------+---------+-------+------+-------------+
      |  1 | SIMPLE      | test_no | const | Uniq_idx_varchar_num | Uniq_idx_varchar_num | 63      | const |    1 | Using index |
      +----+-------------+------------------------+-------+-------------------+-------------------+---------+-------+------+-------------+
      1 row in set (0.00 sec)
      
      mysql>
      mysql>
      mysql> SELECT COUNT(1) FROM test_no WHERE varchar_num=0000194701461220130201115347;
      +----------+
      | COUNT(1) |
      +----------+
      |        1 |
      +----------+
      1 row in set, 1 warning (7.94 sec)
      
      mysql> SELECT COUNT(1) FROM test_no WHERE varchar_num='0000194701461220130201115347';
      +----------+
      | COUNT(1) |
      +----------+
      |        1 |
      +----------+
      1 row in set (0.00 sec)
      

      【讨论】:

      • 加上更多解释,这可能是一个非常有启发性的答案。
      • 如果你不提供报价,它看起来会更慢
      • @ahnbizcad 我猜是因为引用的查询是第二个运行的,并且可以利用第一个被缓存的查询
      • @apricity,不,不是这样。一方面,第二个查询会产生一个单独的缓存条目。但即使你禁用查询缓存,它也会快得多,因为它会进行高效的索引查找而不是索引扫描。查看 EXPLAIN 报告的 rows 字段。
      【解决方案4】:

      AFAIK 这是标准的,但它被认为是不好的做法,因为
      - 在 WHERE 子句中使用它会阻止优化器使用索引(解释计划应该表明这一点)
      - 数据库必须做额外的工作才能将字符串转换为数字
      - 如果你将它用于浮点数('9.4'),如果客户端和服务器使用不同的语言设置(9.4 vs 9,4),你会遇到麻烦

      简而言之:不要这样做(但 YMMV)

      【讨论】:

      • 你从哪里得到没有使用索引的信息?无论有没有引号,我的 EXPLAIN 结果看起来都一样
      • 根据 MySQL 5.6 文档,将 字符串列整数值 进行比较时不使用索引:“用于比较字符串列与number,MySQL 无法在列上使用索引来快速查找值。如果 str_col 是索引字符串列,则在以下语句中执行查找时无法使用索引:" - 参见 dev.mysql.com/doc/refman/5.6/en/type-conversion.html 。尽管 OP 的情况是相反的,但在这种情况下,我仍然不会依赖 RDBMS 来使用索引(AFAIK,至少一个其他主要的 RDBMS (Oracle) 没有)。
      • 它在 MySQL 上运行良好,并且正确使用了索引。就我个人而言,在使用 MySQL 来标准化事物时,我将 everything 用单引号括起来,这只是意味着我不在 MySQL 中使用 BOOL 值。我想当我第一次这样做时,我很紧张,如果它没有用单引号括起来,那么有人会更容易逃脱,但那是罂粟。
      【解决方案5】:

      这不是标准行为。

      对于 MySQL 5.5。这是默认的 SQL 模式

      mysql> select @@sql_mode;
      +------------+
      | @@sql_mode |
      +------------+
      |            |
      +------------+
      1 row in set (0.00 sec)
      

      Oracle 和 PostgreSQL 更严格地使用 ANSI 和 TRADITIONAL。 The SQL Modes MySQL permits must be set IF AND ONLY IF 您想让 SQL 更符合 ANSI。否则,您不必触摸任何东西。我从来没有这样做过。

      【讨论】:

      • 我很好奇 - 这如何回答 OP 的问题?
      • 嗯。是的。我不会将模式更改为 ansi 或传统模式。我有默认的 mysql 设置。
      • 最后一部分说:这种行为标准是否跨不同的 RDBMS?让我澄清一下:这不是不同 RDBMS 的标准行为。
      【解决方案6】:

      这取决于列类型! 如果你运行

      SELECT * FROM `users` WHERE `username` = 0;
      

      在 mysql/maria-db 中,您将获得 username 不为空的所有记录。

      如果列是字符串类型(char、varchar、...),请始终引用值,否则您会得到意想不到的结果!

      【讨论】:

        【解决方案7】:

        你不需要引用数字,但如果你这样做总是一个好习惯。

        【讨论】:

        • 1 OR 1=1 或类似的东西,如果不加引号,可能会导致服务器上不必要的负载或 sql 注入。在 sql 查询中引用所有内容已成为第二天性,但是如果您很挑剔,然后应该引用为数字的 sql 查询不是,那么可能会发生 sql 注入(在线检查这一点以及它有多少受害者)。虽然引用数字确实会在服务器上造成一点负载,但即使您每秒执行大约 10 万次查询,您也只能为 10 万次查询节省总共 11 秒,而每个查询的时间减少到不到一毫秒。
        • 引用确实有助于防止 sql 注入
        • 它确实...它破坏了 sql 查询。我们仍然假设您也将转义数字的数据。
        • @wonk0 Anush 关于 security 的论点似乎完全有效。来自MySQL manual page on Security Guidelines - “一个常见的错误是只保护字符串数据值。记住也要检查数字数据。如果应用程序在用户输入值 234 时生成诸如 SELECT * FROM table WHERE ID=234 之类的查询,用户可以输入值 234 OR 1=1 以使应用程序生成查询 SELECT * FROM table WHERE ID=234 OR 1=1。结果,服务器检索表中的每一行。 ...跨度>
        • ...cont... 这会暴露每一行并导致服务器负载过大。防止此类攻击的最简单方法是在数字常量周围使用单引号:SELECT * FROM table WHERE ID='234'。如果用户输入额外的信息,它就会成为字符串的一部分。在数字上下文中,MySQL 自动将此字符串转换为数字并从中删除任何尾随的非数字字符。”
        【解决方案8】:

        问题是,假设我们有一个名为 users 的表,如果您运行此查询,它有一个名为 current_balance 的 FLOAT 类型的列:

        UPDATE `users` SET `current_balance`='231608.09' WHERE `user_id`=9;
        

        current_balance 字段将更新为 231608,因为 MySQL 进行了四舍五入,类似地,如果您尝试以下查询:

        UPDATE `users` SET `current_balance`='231608.55' WHERE `user_id`=9;
        

        current_balance 字段将更新为 231609

        【讨论】:

        • 不,current_balance 字段不会更新为 231608 或 231609,无论是否使用引号,都会分别更新为 231608.09375 和 231608.546875。通过 SELECT user_id, FORMAT(current_balance,24) FROM users 找出自己
        • 永远不要将浮点数用于绝对精度,例如帐户余额。使用小数列类型。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-11-25
        • 1970-01-01
        • 2020-10-06
        • 1970-01-01
        • 2010-12-15
        • 1970-01-01
        • 2014-08-21
        相关资源
        最近更新 更多