PHP在TIDB上遇到的坑

原因不多说, 总之部署了两套TIDB的集群环境.

TIDB现在相当的火, 还去过很多次pingcap的在西小口的线下meepup, 各种牛人的分享.

今天主要是总结一些在tidb中遇到的坑, 有PHP的, 也有TIDB的, 但是可能是自己坑自己的.

主要问题如下:

自增ID

事务提交

prepare语句

目录:

自增ID

事务提交

prepare语句

在lavavel中使用TIDB

总结

自增ID

TIDB的自增ID不是按照时间增序的, 那么按照时间进行分页的需求就比较坑了.

比如有2台TIDB, t1 和 t2,

那么ID 1-5000 在t1, 5001-10000 在t2,

10001-15000又在t1, 15001-20000在t2.

如果新增两条纪录, 它们的ID会是1, 5001.

解决办法是在每张表新增一个字段, 然后到发号器去取一下这个ID.

而表的ID, TIDB建议使用UUID, 而不使用TIDB的自增ID.

个人觉得, 在要满足业务的条件是用UUID+发号器.

比如一个简单的实现为:

以表名在redis保存一个key, 每新增一条纪录, 使用incr命令取一下ID, 插入到表中.

事务提交

具体问题是PDO的commit()函数始终返回true, 所以会导致业务判断失误.

解决办法是使用rawSql, 其php模版如下:

$options = $options = [

PDO::ATTR_CASE => PDO::CASE_NATURAL,

PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,

PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,

PDO::ATTR_STRINGIFY_FETCHES => false,

PDO::ATTR_EMULATE_PREPARES => true,

];

$pdo = new PDO('', '', '', $options);

$pdo->exec('START TRANSACTION'); // will throw an exception if failed

try {

// SQLs

// ...

$pdo->exec('COMMIT'); // will throw an exception

} catch (Exception $e) {

$ret = $pdo->exec('ROLLBACK');

throw $e;

} catch (Throwable $e) {

$ret = $pdo->exec('ROLLBACK');

throw $e;

}

而且commit()函数在返回true后, 虽然事实事务是失败的, 但是也没有设置errorCode,

所以办法只有使用rawSql.

TIDB还有一个事务问题, 是理论上能够成功提交的事务, 实际上多次提交也成功不了,

在我这里是一次都没成功过, 而且因为PHP-commit()函数的问题, 又会导致误判.

但是换了sql的参数后, 就能成功提交.

这个问题暂时无解, 但是业务上也不能出错才行, 可以通过rawSql-COMMIT暂时处理下.

prepare语句

用PDO的可能会使用prepare语句, 但是在TIDB中遇到的一个问题就是,

用prepare语句查出来的纪录有时会比理论的少几条.

具体的问题我也给TIDB提了一个issue.

解决办法就是将prepare语句转换成rawSql, 这个PHP已经帮我们做了,

用法如下:

$options = $options = [

PDO::ATTR_EMULATE_PREPARES => true,

];

$pdo = new PDO('', '', '', $options);

PDO::ATTR_EMULATE_PREPARES 启用或禁用预处理语句的模拟。

有些驱动不支持或有限度地支持本地预处理。

使用此设置强制PDO总是模拟预处理语句(如果为 TRUE ),

或试着使用本地预处理语句(如果为 FALSE)。

如果驱动不能成功预处理当前查询,

它将总是回到模拟预处理语句上。

需要 bool 类型。

在lavavel中使用TIDB

现在Lavavel应该是PHP中最火的框架了, 在我使用lavavel+TIDB遇到的问题,

基本就是上面提到的一些问题, 我给出的解决办法如下:

修改database配置, 使其不使用prepare语句

重点在mysql数组中增加options字段, 其实为

'options' => [

PDO::ATTR_EMULATE_PREPARES => true,

],

file: config/database.php

return [

'fetch' => PDO::FETCH_CLASS,

'default' => env('DB_CONNECTION', 'mysql'),

'connections' => [

'testing' => [

'driver' => 'sqlite',

'database' => ':memory:',

],

'sqlite' => [

'driver' => 'sqlite',

'database' => env('DB_DATABASE', storage_path('database.sqlite')),

'prefix' => env('DB_PREFIX', ''),

],

'mysql' => [

'read' => [

'host' => env('DB_READ_HOST','localhost'),

],

'write' => [

'host' => env('DB_WRITE_HOST','localhost'),

],

'driver' => 'mysql',

'port' => env('DB_PORT', 3306),

'database' => env('DB_DATABASE', 'forge'),

'username' => env('DB_USERNAME', 'forge'),

'password' => env('DB_PASSWORD', ''),

'charset' => 'utf8mb4',

'collation' => 'utf8mb4_unicode_ci',

'prefix' => env('DB_PREFIX', ''),

'timezone' => env('DB_TIMEZONE', '+08:00'),

'strict' => false,

'options' => [

PDO::ATTR_EMULATE_PREPARES => true,

],

],

'pgsql' => [

'driver' => 'pgsql',

'host' => env('REDSHIFT_HOST', 'localhost'),

'port' => env('REDSHIFT_PORT', 5432),

'database' => env('REDSHIFT_DATABASE', 'forge'),

'username' => env('REDSHIFT_USERNAME', 'forge'),

'password' => env('REDSHIFT_PASSWORD', ''),

'charset' => 'utf8',

'prefix' => env('DB_PREFIX', ''),

'schema' => 'public',

],

'sqlsrv' => [

'driver' => 'sqlsrv',

'host' => env('DB_HOST', 'localhost'),

'database' => env('DB_DATABASE', 'forge'),

'username' => env('DB_USERNAME', 'forge'),

'password' => env('DB_PASSWORD', ''),

'prefix' => env('DB_PREFIX', ''),

],

'mongodb' => [

'driver' => 'mongodb',

'host' => explode(',', env('MONGODB_HOST', 'localhost')),

'port' => env('MONGODB_PORT', 27017),

'username' => env('MONGODB_USERNAME', 'accountUser'),

'password' => env('MONGODB_PASSWORD', 'password'),

'database' => env('MONGODB_DATABASE', 'users'),

'options' => ['replicaSet' => env('MONGODB_RS_NAME')]

],

],

'migrations' => 'migrations',

'redis' => [

'cluster' => env('REDIS_CLUSTER', false),

'default' => [

'host' => env('REDIS_HOST', '127.0.0.1'),

'port' => env('REDIS_PORT', 6379),

'database' => intval(env('REDIS_DATABASE', 0)),

'password' => env('REDIS_PASSWORD', null)

]

],

];

添加自定义的TidbConnection, 使其使用SQL语句进行提交

新建文件"app/Database/TidbConnection.php", 其内容如下:

namespace App\Database;

use Closure;

use Exception;

use Throwable;

use Illuminate\Database\MySqlConnection;

class TidbConnection extends MySqlConnection

{

public function transaction(Closure $callback)

{

$this->beginTransaction();

// We'll simply execute the given callback within a try / catch block

// and if we catch any exception we can rollback the transaction

// so that none of the changes are persisted to the database.

try {

$result = $callback($this);

$this->commit();

}

// If we catch an exception, we will roll back so nothing gets messed

// up in the database. Then we'll re-throw the exception so it can

// be handled how the developer sees fit for their applications.

catch (Exception $e) {

$this->rollBack();

throw $e;

} catch (Throwable $e) {

$this->rollBack();

throw $e;

}

return $result;

}

/**

* Start a new database transaction.

*

* @return void

* @throws Exception

*/

public function beginTransaction()

{

++$this->transactions;

if ($this->transactions == 1) {

try {

$this->pdo->exec('START TRANSACTION');

} catch (Exception $e) {

--$this->transactions;

throw $e;

}

} elseif ($this->transactions > 1 && $this->queryGrammar->supportsSavepoints()) {

$this->pdo->exec(

$this->queryGrammar->compileSavepoint('trans'.$this->transactions)

);

}

$this->fireConnectionEvent('beganTransaction');

}

/**

* Commit the active database transaction.

*

* @return void

*/

public function commit()

{

if ($this->transactions == 1) {

$this->pdo->exec('COMMIT');

}

--$this->transactions;

$this->fireConnectionEvent('committed');

}

/**

* Rollback the active database transaction.

*

* @return void

*/

public function rollBack()

{

if ($this->transactions == 1) {

$this->pdo->exec('ROLLBACK');

} elseif ($this->transactions > 1 && $this->queryGrammar->supportsSavepoints()) {

$this->pdo->exec(

$this->queryGrammar->compileSavepointRollBack('trans'.$this->transactions)

);

}

$this->transactions = max(0, $this->transactions - 1);

$this->fireConnectionEvent('rollingBack');

}

}

然后注入到容器里面, 方法如下:

编辑"app/Providers/AppServiceProvider.php", 其内容如下:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider

{

public function register()

{

$this->app->bind('db.connection.mysql', function ($app, $parameters) {

$rc = new \ReflectionClass('App\Database\TidbConnection');

return $rc->newInstanceArgs($parameters);

});

}

}

最后确保框架引入了AppServiceProvider

Lumen需要编辑的文件"bootstrap/app.php"

$app->register(App\Providers\AppServiceProvider::class);

Laravel一般不需要修改内容, 它已经在"config/app.php"引入了"AppServiceProvider"类.

总结

虽然在TIDB听了他们开发者讲了很多细节,

但是离真正用好TIDB还有很长的一段距离.

目前遇到的问题, 都可以使用各种方式"解决",

但是或多或少都没有达到不修改代码直接迁移到TIDB的地步,

后续有什么其他的问题, 我也会一并整理到这篇文章.

meep php,PHP在TIDB上遇到的坑相关推荐

  1. spark 写tidb_优秀的数据工程师,怎么用Spark在TiDB上做OLAP分析

    TiDB 是一款定位于在线事务处理/在线分析处理的融合型数据库产品,实现了一键水平伸缩,强一致性的多副本数据安全,分布式事务,实时 OLAP 等重要特性. TiSpark 是 PingCAP 为解决用 ...

  2. TiDB上百T数据拆分实践

    作者:杨家鑫 原文来源: https://tidb.net/blog/57fbca58 TiDB上百T数据拆分实践 背景 提高TiDB可用性,需要把多点已有上百T TiDB集群拆分出2套 挑战 1.现 ...

  3. H5拍照、预览、压缩、上传采坑记录

    H5拍照.预览.压缩.上传采坑记录 公司项目前段时间需要实现手机拍照上传的功能,本来以为用createObjectURL和canvas可以很轻松的实现,结果发现问题多多,特此记录下来. DEMO预览( ...

  4. 关于在树莓派内存卡配置上遇到的坑

    关于在树莓派内存卡配置上遇到的坑 配置树莓派的内存卡尽量选择不超过64G的,也就是格式为FAT32的,超过64G的格式为exFAT,比如128G的内存卡,所以在格式化时要注意格式类型,但是并不是这样就 ...

  5. twine上传的坑:pkg_resources.DistributionNotFound: The ‘docutils>=0.13.1‘ distribution was not found

    twine上传的坑:pkg_resources.DistributionNotFound: The 'docutils>=0.13.1' distribution was not found 一 ...

  6. Mac因为没有安全弹出下次连接不上移动硬盘的坑

    Mac因为没有安全弹出下次连接不上移动硬盘的坑 打开Mac OSX原生的读写NTFS功能 因为Mac内存小 所以经常会用到移动硬盘! 但是Mac对移动硬盘不友好! 经常遇到因为没有安全弹出下次使用的时 ...

  7. gogoprotobuf在windows上遇见的坑

    gogoprotobuf在windows上遇见的坑 闲话一下 准备工作 创建你的工程目录 生成go文件 我们还想要更简洁的代码 总结 闲话一下 最近打算做一个联网的小游戏,通讯协议决定使用protob ...

  8. jquery.ajax上传个数限制,关于jquery ajax上传的坑

    @(呵呵) 今天主要介绍processData: false, contentType: false, 这两个参数--processData 默认情况下,通过data选项传递进来的数据,如果是一个对象 ...

  9. mysql sock golang_golang thrift 总结一下网络上的一些坑

    我们以hello world来大概分析一下golang中的thrift包,并且扒一扒网络上有关thrift的一些坑 查看源码,服务器定义如下:(详见simple_server.go文件) type T ...

最新文章

  1. 区块链第二层扩容方案Plasma和Rollups
  2. 【29.42%】【POJ 1182】食物链
  3. 网工协议基础(1) OSI七层模型
  4. MySQL数据库同步小工具(Java实现)
  5. 利用mvc 模型绑定验证方法验证普通类对象数据是否合法
  6. python 调用vba 参数 保存表格_Jupyter Notebooks嵌入Excel并使用Python替代VBA宏
  7. MySQL 慌了!这个分库分表方法论,要火了?
  8. UltraEdit 21 for Mac(超好用的高级文本编辑器)
  9. 在没有显示器和IP未知的情况下如何使用树莓派
  10. 计算机无法关机和重启怎么办,关于电脑无法关机怎么办
  11. 折腾了好久 ORA-00904: : 无效的标识符
  12. 不定宽高,实现盒子左右垂直居中
  13. 解决REFERENCEERROR: primordials is not defined问题
  14. Android 9.0 (P)
  15. Matlab:创建字符串数组
  16. g54y6huj6yh
  17. Hadoop实战——MapReduce对英文单词文本进行统计和排序(超详细教学,算法分析)
  18. 阴阳师服务器维护,《阴阳师》手游10月24日维护更新公告
  19. Linux常见命令:与系统管理有关的命令(转)
  20. CentOS安装mysql*.rpm提示conflicts with file from package的解决办法

热门文章

  1. 【大唐杯学习超快速入门】5G技术原理仿真教学——5G网络开通调测与车联网
  2. H5移动端完美实现点击复制文本的解决方法,已经自测!
  3. 从MapGIS K9到MapGIS 10到MapGIS 10.3 Server
  4. HDU6112今夕何夕(Java写法)
  5. linux mysql下载安装步骤(方便下次安装)
  6. V831体验—烧录系统
  7. 动画演示!红黑树解析
  8. python利用WPS接口之excel中图片写入
  9. okcc呼叫中心如何隐藏号码?
  10. finereport 字符串拼接函数