`INSERT INTO SELECT` 根据运行查询的客户端转换值

`INSERT INTO SELECT` converts values depending on the client which runs the query

我正在 运行 进行一个非常基本的查询,我希望它正确地失败,并且当 运行 通过任何 MySQL 客户端时,它会按预期进行。但是当 运行 在 PHP 脚本中使用 mysqli 时,它会进行 st运行ge 数据转换。

下一个查询:

INSERT INTO gk_process_log (process_name, id_instance, date_start) select 'TestLog', NULL, '2020-10-19 14:29:11';");

列 id_instance 是 NOT NULL,因此查询应该失败 - 完整的 table 定义在底部。正如我所说,当 运行 通过任何 MySQL 客户端时,它“正常”失败。

但是当我 运行 从 PHP 脚本中查询时:

mysqli_query($this->getConnection(), "INSERT INTO gk_process_log (process_name, id_instance, date_start) select 'TestLog', NULL, '2020-10-19 14:29:11';");

它确实在 NOT NULL 列中插入了一个 zero 查询执行很简单,如图所示,不涉及参数绑定。

任何人都可以解释为什么会发生这种情况,我该如何解决?

Table定义:

            CREATE TABLE gk_process_log (
              id_process_log int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
              process_name varchar(50) NOT NULL,
              id_instance int(11) NOT NULL,
              iteration_total INT NULL,
              iteration VARCHAR(255) NULL,
              iteration_step VARCHAR(255) NULL,
              is_finished TINYINT(1) NULL,
              date_start DATETIME NOT NULL,
              date_iteration VARCHAR(255) NULL,
              date_iteration_step VARCHAR(255) NULL,
              date_end DATETIME NULL DEFAULT NULL,
              state_data VARCHAR(255) NULL,
              result_data VARCHAR(255) NULL,
              UNIQUE INDEX instance (process_name, id_instance),
              INDEX (process_name),
              INDEX (id_instance),
              INDEX (is_finished)
            ) ENGINE=InnoDB CHARSET=utf8

“值转换”行为是一个有问题的 MySQL 功能,它由“SQL 模式”配置。来自 the manual:

If strict mode is not in effect, MySQL inserts adjusted values for invalid or missing values and produces warnings

因此,在将 NULL 转换为零的情况下,未启用严格模式,因此 MySQL 将无效值自动转换为字段默认值以解决 NOT NULL 约束。

但是,只有在使用 INSERT INTO SELECT 语法时,才会在 NOT NULL 约束条件下观察到这种情况。当使用常规 INSERT 语法时,无论 SQL 模式如何,都会出现 'Column cannot be null' 错误。

关于客户端依赖行为,答案在the SQL mode。唯一可以改变同一查询在不同连接上的不同工作方式的是其中一个更改了 SQL 模式,因此根据连接的当前 SQL 模式设置,服务器的行为有所不同。可以通过 运行 select @@sql_mode; 检查 - 显示当前连接的当前 SQL 模式 - 和 select @@GLOBAL.sql_mode; - 显示全局 SQL 模式设置服务器,这将是创建的任何新连接的默认模式,直到它被更改。

在 OP 的场景中,他用于手动 SQL 查询构建和测试的 MySQL 客户端是 IntelliJ IDEA,内置 Java MySQL 连接器,并且客户端的实现似乎正在更改其连接的 SQL 模式。然后,通过 IntelliJ IDEA 查询 运行 的行为与通过 PHP 的 mysqli 查询 运行 时观察到的行为不同。 OP 将 mysqli 行为解释为非标准行为,但它确实是在服务器上设置的 SQL 模式,而 IntelliJ IDEA 的行为在这里是错误的。这种意外的 IntelliJ IDEA 行为已与其开发人员共享 here

根据MySQL forum answers

Note that Connector/J sets SQL_MODE=STRICT_TRANS_TABLES if the connection property jdbcCompliantTruncation is 'true' (which is by default). This is the only change to SQL_MODE that happens under the hood.

同样的可以在MySQL Connector/J source code中看到,见ConnectionImpl.javasetupServerForTruncationChecks方法