Laravel 使用 \PDO::ATTR_EMULATE_PREPARES 时出现数据不匹配错误 => true
Laravel data mismatch error while using \PDO::ATTR_EMULATE_PREPARES => true
我们在 Php Laravel 中构建了应用程序,对于数据库,我们使用 postgres sql。在 postgres 之上,我们还配置了 pgBouncer,通过管理一个可供任何应用程序使用的空闲连接池来限制服务器端的最大连接数。
现在,我们面临应用程序 (Php Laravel) 中使用的布尔值 (True(0),False(1)) 的问题。当执行任何 CRUD 操作时,它会给出以下错误。在下面的错误列中 "revoked" 是布尔类型。
列 \"revoked\" 是布尔类型,但表达式是整数类型
您将需要重写或转换表达式。 (SQL: \"revoked\", \"created_at\") 值 (0, 2020-02-07 06:09:06)
现在经过探索,我开始知道布尔值需要被认为是 pgBouncer 的字符串。因此,我对位于“\vendor\laravel\framework\src\Illuminate\Database”的 connection.php 文件进行了更改。我已更改代码以考虑如下所述的布尔值。
public function bindValues($statement, $bindings)
{
foreach ($bindings as $key => $value) {
//if(is_bool($value))
$statement->bindValue(
is_string($key) ? $key : $key + 1, $value,
//is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR
is_int($value) ? PDO::PARAM_INT : is_bool($value) ? PDO::PARAM_STR : PDO::PARAM_STR
);
}
}
经过上述更改后,布尔值的错误已解决。
但是,现在我在服务器上遇到了奇怪的问题,当我检查数据库日志错误时,我总是得到以下错误。
错误:预备语句"pdo_stmt_00000001"已经存在
声明:设置名称 'utf8'
错误:准备好的语句 "pdo_stmt_00000001" 不存在
语句:取消分配 pdo_stmt_00000001
这真的很奇怪,在浏览互联网后,我在我的 database.php 文件中做了以下更改,以禁用准备语句。
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
'options' => [
\PDO::ATTR_EMULATE_PREPARES => true
]
]
之所以看到 ATTR_EMULATE_PREPARES => true 是因为我在 "[=] 中设置了 "Transaction" 模式45=]pgbouncer.ini" 文件.
现在,要使准备好的语句在 Transaction 模式下工作,需要 PgBouncer 在内部跟踪它们,而它并没有这样做。因此,在此模式下继续使用 PgBouncer 的唯一方法是禁用客户端中的准备语句,在我的例子中是 PHP Laravel 并且我已经在“database.php" 文件,如上代码所示建立连接。
我已经尝试了 http://www.pgbouncer.org/faq.html#how-to-use-prepared-statements-with-transaction-pooling 中给出的所有选项,但它没有解决显示的 prepare statment 错误在数据库日志中。
错误:预备语句"pdo_stmt_00000001"已经存在
声明:设置名称 'utf8'
错误:准备好的语句 "pdo_stmt_00000001" 不存在
语句:取消分配 pdo_stmt_00000001
请指导我相同的错误以及需要进行哪些进一步的设置。这些错误在客户端生产服务器上,我们无法在生产服务器中继续处理这些错误。
请尽早给我宝贵的反馈,因为我在 5 天前就遇到了这个问题,并尝试遇到的所有选项。
谢谢!
首先,您无需修改供应商代码,而是可以使用模型中的属性转换。
来自laravel.com/docs/master/eloquent-mutators#attribute-casting
The $casts property on your model provides a convenient method of converting attributes to common data types. The $casts property should be an array where the key is the name of the attribute being cast and the value is the type you wish to cast the column to. The supported cast types are: integer, real, float, double, decimal:, string, boolean, object, array, collection, date, datetime, and timestamp. When casting to decimal, you must define the number of digits (decimal:2).
To demonstrate attribute casting, let's cast the is_admin attribute,
which is stored in our database as an integer (0 or 1) to a boolean
value:
所以在你的情况下,你需要将 revoked
添加到你的 Eloquent 模型 $casts
属性如下:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class YourModel extends Model
{
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'revoked' => 'boolean',
];
}
对于你的 pgBouncer 问题,pgBouncer 似乎有一个内部问题 transaction pooling and prepared statements,
发件人:whosebug.com/a/7612639/7047493
This turned out to be a pgBouncer issue that occurs when using anything other than session pooling. We were using transaction pooling, which apparently can't support prepared statements. By switching to session pooling, we got around the issue.
1) 首先,您需要更改您在 database.php 的 pgsql 数组中的选项中提供的 PDO 选项,正确的方法如下所示。
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5434'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
'options' => [
PDO::ATTR_EMULATE_PREPARES => true
]
]
2) 其次,也是最重要的一点是确保将 "ATTR_EMULATE_PREPARES" 设置为 "true" 并尝试在 [ 中尝试连接的每个数据库连接=29=] 文件.
例如,
'test' => [
'driver' => 'pgsql',
'host' => env('test', '127.0.0.1'),
'port' => env('test', '5434'),
'database' => env('DB_TEST_DATABASE', 'test'),
'username' => env('DB_USERNAME', 'test'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
'options' => [
PDO::ATTR_EMULATE_PREPARES => true
]
],
'test1' => [
'driver' => 'pgsql',
'host' => env('test1', '127.0.0.1'),
'port' => env('test1', '5434'),
'database' => env('DB_TEST1_DATABASE', 'test1'),
'username' => env('DB_USERNAME', 'test'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
'options' => [
PDO::ATTR_EMULATE_PREPARES => true
]
]
请确保对您在应用程序中建立的每个数据库连接使用 "ATTR_EMULATE_PREPARES" 为真,在您的评论中,您仅与 "pgsql" 建立连接,强调 postgres sql仅连接,而不是与您的应用程序通信的数据库(在 postgres 中)。
希望这能帮助您解决问题。享受吧!!!
None 之前的答案在我们的案例中完全有效。在我们的设置 (Laravel + PostgreSQL + pgBouncer) 中,我们在 database.php 文件中启用了这两个设置。目标是使我们的 php 后端与 pgBouncer
兼容,这就是我们所做的:
// database.php
'options' => array(
PDO::ATTR_EMULATE_PREPARES => true
),
'binary_parameters' => 'yes', // not sure if this one is necessary
这 2 个设置部分起作用,这意味着我们能够 运行 我们的后端而无需再获得 prepared statement does not exist
。对我们来说不幸的是,我们当时得到了 datatype mismatch: 7 ERROR: column “xxx” is of type boolean but expression is of type integer
就像 Nileshsinh Rathod 一样。
希望对我们来说,我们在 Github 上遇到了这个 post,它为我们解决了所有问题。目标是覆盖默认的 PostgresConnector。
下面是我们所做工作的回顾:
在我们的项目中添加这 3 个文件:
- https://github.com/umbrellio/laravel-pg-extensions/blob/master/src/Connectors/ConnectionFactory.php
- https://github.com/umbrellio/laravel-pg-extensions/blob/master/src/UmbrellioPostgresProvider.php
- https://github.com/umbrellio/laravel-pg-extensions/blob/master/src/PostgresConnection.php
- 在这个文件中,我们只保留了
bindValues
和 prepareBindings
函数。
然后,在我们的config/app.php
中,我们像这样注册了PostgresProvider
'providers' => [
App\Providers\ScPostgresProvider::class,
],
最后,我们在 AppServiceProvider 文件中注释掉了这一行,以确保只有新的才会被注册
public function register()
{
// not used anymore since we use our our own connector
// $this->app->bind('db.connector.pgsql', OldPostgresConnector::class);
}
非常感谢 Github 上 Umbrellio 团队的 post,希望这个回答能帮助到其他人!
我们在 Php Laravel 中构建了应用程序,对于数据库,我们使用 postgres sql。在 postgres 之上,我们还配置了 pgBouncer,通过管理一个可供任何应用程序使用的空闲连接池来限制服务器端的最大连接数。
现在,我们面临应用程序 (Php Laravel) 中使用的布尔值 (True(0),False(1)) 的问题。当执行任何 CRUD 操作时,它会给出以下错误。在下面的错误列中 "revoked" 是布尔类型。
列 \"revoked\" 是布尔类型,但表达式是整数类型 您将需要重写或转换表达式。 (SQL: \"revoked\", \"created_at\") 值 (0, 2020-02-07 06:09:06)
现在经过探索,我开始知道布尔值需要被认为是 pgBouncer 的字符串。因此,我对位于“\vendor\laravel\framework\src\Illuminate\Database”的 connection.php 文件进行了更改。我已更改代码以考虑如下所述的布尔值。
public function bindValues($statement, $bindings)
{
foreach ($bindings as $key => $value) {
//if(is_bool($value))
$statement->bindValue(
is_string($key) ? $key : $key + 1, $value,
//is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR
is_int($value) ? PDO::PARAM_INT : is_bool($value) ? PDO::PARAM_STR : PDO::PARAM_STR
);
}
}
经过上述更改后,布尔值的错误已解决。
但是,现在我在服务器上遇到了奇怪的问题,当我检查数据库日志错误时,我总是得到以下错误。
错误:预备语句"pdo_stmt_00000001"已经存在 声明:设置名称 'utf8' 错误:准备好的语句 "pdo_stmt_00000001" 不存在 语句:取消分配 pdo_stmt_00000001
这真的很奇怪,在浏览互联网后,我在我的 database.php 文件中做了以下更改,以禁用准备语句。
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
'options' => [
\PDO::ATTR_EMULATE_PREPARES => true
]
]
之所以看到 ATTR_EMULATE_PREPARES => true 是因为我在 "[=] 中设置了 "Transaction" 模式45=]pgbouncer.ini" 文件.
现在,要使准备好的语句在 Transaction 模式下工作,需要 PgBouncer 在内部跟踪它们,而它并没有这样做。因此,在此模式下继续使用 PgBouncer 的唯一方法是禁用客户端中的准备语句,在我的例子中是 PHP Laravel 并且我已经在“database.php" 文件,如上代码所示建立连接。
我已经尝试了 http://www.pgbouncer.org/faq.html#how-to-use-prepared-statements-with-transaction-pooling 中给出的所有选项,但它没有解决显示的 prepare statment 错误在数据库日志中。
错误:预备语句"pdo_stmt_00000001"已经存在 声明:设置名称 'utf8' 错误:准备好的语句 "pdo_stmt_00000001" 不存在 语句:取消分配 pdo_stmt_00000001
请指导我相同的错误以及需要进行哪些进一步的设置。这些错误在客户端生产服务器上,我们无法在生产服务器中继续处理这些错误。
请尽早给我宝贵的反馈,因为我在 5 天前就遇到了这个问题,并尝试遇到的所有选项。
谢谢!
首先,您无需修改供应商代码,而是可以使用模型中的属性转换。
来自laravel.com/docs/master/eloquent-mutators#attribute-casting
The $casts property on your model provides a convenient method of converting attributes to common data types. The $casts property should be an array where the key is the name of the attribute being cast and the value is the type you wish to cast the column to. The supported cast types are: integer, real, float, double, decimal:, string, boolean, object, array, collection, date, datetime, and timestamp. When casting to decimal, you must define the number of digits (decimal:2).
To demonstrate attribute casting, let's cast the is_admin attribute, which is stored in our database as an integer (0 or 1) to a boolean value:
所以在你的情况下,你需要将 revoked
添加到你的 Eloquent 模型 $casts
属性如下:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class YourModel extends Model
{
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'revoked' => 'boolean',
];
}
对于你的 pgBouncer 问题,pgBouncer 似乎有一个内部问题 transaction pooling and prepared statements,
发件人:whosebug.com/a/7612639/7047493
This turned out to be a pgBouncer issue that occurs when using anything other than session pooling. We were using transaction pooling, which apparently can't support prepared statements. By switching to session pooling, we got around the issue.
1) 首先,您需要更改您在 database.php 的 pgsql 数组中的选项中提供的 PDO 选项,正确的方法如下所示。
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5434'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
'options' => [
PDO::ATTR_EMULATE_PREPARES => true
]
]
2) 其次,也是最重要的一点是确保将 "ATTR_EMULATE_PREPARES" 设置为 "true" 并尝试在 [ 中尝试连接的每个数据库连接=29=] 文件.
例如,
'test' => [
'driver' => 'pgsql',
'host' => env('test', '127.0.0.1'),
'port' => env('test', '5434'),
'database' => env('DB_TEST_DATABASE', 'test'),
'username' => env('DB_USERNAME', 'test'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
'options' => [
PDO::ATTR_EMULATE_PREPARES => true
]
],
'test1' => [
'driver' => 'pgsql',
'host' => env('test1', '127.0.0.1'),
'port' => env('test1', '5434'),
'database' => env('DB_TEST1_DATABASE', 'test1'),
'username' => env('DB_USERNAME', 'test'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
'options' => [
PDO::ATTR_EMULATE_PREPARES => true
]
]
请确保对您在应用程序中建立的每个数据库连接使用 "ATTR_EMULATE_PREPARES" 为真,在您的评论中,您仅与 "pgsql" 建立连接,强调 postgres sql仅连接,而不是与您的应用程序通信的数据库(在 postgres 中)。
希望这能帮助您解决问题。享受吧!!!
None 之前的答案在我们的案例中完全有效。在我们的设置 (Laravel + PostgreSQL + pgBouncer) 中,我们在 database.php 文件中启用了这两个设置。目标是使我们的 php 后端与 pgBouncer
兼容,这就是我们所做的:
// database.php
'options' => array(
PDO::ATTR_EMULATE_PREPARES => true
),
'binary_parameters' => 'yes', // not sure if this one is necessary
这 2 个设置部分起作用,这意味着我们能够 运行 我们的后端而无需再获得 prepared statement does not exist
。对我们来说不幸的是,我们当时得到了 datatype mismatch: 7 ERROR: column “xxx” is of type boolean but expression is of type integer
就像 Nileshsinh Rathod 一样。
希望对我们来说,我们在 Github 上遇到了这个 post,它为我们解决了所有问题。目标是覆盖默认的 PostgresConnector。 下面是我们所做工作的回顾:
在我们的项目中添加这 3 个文件:
- https://github.com/umbrellio/laravel-pg-extensions/blob/master/src/Connectors/ConnectionFactory.php
- https://github.com/umbrellio/laravel-pg-extensions/blob/master/src/UmbrellioPostgresProvider.php
- https://github.com/umbrellio/laravel-pg-extensions/blob/master/src/PostgresConnection.php
- 在这个文件中,我们只保留了
bindValues
和prepareBindings
函数。
- 在这个文件中,我们只保留了
然后,在我们的
config/app.php
中,我们像这样注册了PostgresProvider
'providers' => [ App\Providers\ScPostgresProvider::class, ],
最后,我们在 AppServiceProvider 文件中注释掉了这一行,以确保只有新的才会被注册
public function register() { // not used anymore since we use our our own connector // $this->app->bind('db.connector.pgsql', OldPostgresConnector::class); }
非常感谢 Github 上 Umbrellio 团队的 post,希望这个回答能帮助到其他人!