Laravel v5.4.18 中的一個提交,導致的 BUG,因為新增了錯誤的單測,導致沒辦法輕易修改,這裡提醒大家,使用時需要謹慎,以免採坑。
BUG 重現
1. increment extra 後再進行 save 操作,會執行兩句SQL
我們先寫一段沒有 extra 資料的程式碼,進行測試
DB::enableQueryLog();
$model = UserExt::query()->find(101);
$model->increment('count');
$model->save();
dump(DB::getQueryLog());
透過測試得知,以上程式碼只會生成兩段 SQL,分別是
select * from `user_ext` where `user_ext`.`id` = ? limit 1
update `user_ext` set `count` = `count` + 1, `user_ext`.`updated_at` = ? where `id` = ?
然後讓我們修改測試程式碼
DB::enableQueryLog();
$model = UserExt::query()->find(101);
$model->increment('count', 1, [
'str' => uniqid()
]);
$model->save();
dump(DB::getQueryLog());
這時,會生成以下三段 SQL
select * from `user_ext` where `user_ext`.`id` = ? limit 1
update `user_ext` set `count` = `count` + 1, `str` = ?, `user_ext`.`updated_at` = ? where `id` = ?
update `user_ext` set `str` = ?, `user_ext`.`updated_at` = ? where `id` = ?
且第二段和第三段 SQL 中,str 的值是一致的。這個問題的主要原因,便是 extra 裡的資料不會被同步到 original 中,就導致第二次 save 計算 dirty 的時候,出現了BUG。
2. getChanges 表現不一致
經過第一個 BUG 的重現,那麼第二個問題也就很容易想到了,就是 getChanges 方法。
讓我們繼續編寫程式碼測試
$model = UserExt::query()->find(101);
$model->increment('count');
dump($model->getChanges());
以上程式碼會輸出以下資料,可見還是符合預期的
array:1 [▼
"count" => 4
]
讓我們繼續修改程式碼,在 increment
前增加一次賦值
DB::enableQueryLog();
$model = UserExt::query()->find(101);
$model->str = uniqid();
$model->increment('count');
dump($model->getChanges());
dump(DB::getQueryLog());
會得到以下輸出
array:2 [▼
"count" => 7
"str" => "5febf2dc798ed"
]
看似沒有問題,但讓我們檢查一下 SQL
select * from `user_ext` where `user_ext`.`id` = ? limit 1
update `user_ext` set `count` = `count` + 1, `user_ext`.`updated_at` = ? where `id` = ?
卻發現,並沒有修改 str 的資料,那顯然 getChanges 與預期不符。
實際上,increment 在設計上,並沒有想要修改前面 setter 的資料,但這種情況下,我們 getChanges 便也不能把 str 算進來。
讓我們繼續修改程式碼
DB::enableQueryLog();
$model = UserExt::query()->find(101);
$model->str = uniqid();
$model->increment('count');
dump($model->getChanges());
$model->save();
dump($model->getChanges());
dump(DB::getQueryLog());
兩次 getChanges 輸出如下
array:2 [▼
"count" => 9
"str" => "5febf3d6418e8"
]
array:2 [▼
"str" => "5febf3d6418e8"
"updated_at" => "2020-12-30 03:28:22"
]
可見兩次 getChanges 中,str 的值是一致的。
save 的時候會把 updated_at 算進來,而
increment
的時候是不會算updated_at
,這裡至少行為一致,可以作為後續的最佳化項。
輸出的 SQL 如下
select * from `user_ext` where `user_ext`.`id` = ? limit 1
update `user_ext` set `count` = `count` + 1, `str` = ?, `user_ext`.`updated_at` = ? where `id` = ?
update `user_ext` set `str` = ?, `user_ext`.`updated_at` = ? where `id` = ?
Hyperf 是基於 Swoole 4.5+
實現的高效能、高靈活性的 PHP 協程框架,內建協程伺服器及大量常用的元件,效能較傳統基於 PHP-FPM
的框架有質的提升,提供超高效能的同時,也保持著極其靈活的可擴充套件性,標準元件均基於 PSR 標準 實現,基於強大的依賴注入設計,保證了絕大部分元件或類都是 可替換
與 可複用
的。
框架元件庫除了常見的協程版的 MySQL 客戶端
、Redis 客戶端
,還為您準備了協程版的 Eloquent ORM
、WebSocket 服務端及客戶端
、JSON RPC 服務端及客戶端
、GRPC 服務端及客戶端
、OpenTracing(Zipkin, Jaeger) 客戶端
、Guzzle HTTP 客戶端
、Elasticsearch 客戶端
、Consul、Nacos 服務中心
、ETCD 客戶端
、AMQP 元件
、Nats 元件
、Apollo、ETCD、Zookeeper、Nacos 和阿里雲 ACM 的配置中心
、基於令牌桶演算法的限流器
、通用連線池
、熔斷器
、Swagger 文件生成
、Swoole Tracker
、Blade、Smarty、Twig、Plates 和 ThinkTemplate 檢視引擎
、Snowflake 全域性ID生成器
、Prometheus 服務監控
等元件,省去了自己實現對應協程版本的麻煩。
Hyperf 還提供了 基於 PSR-11 的依賴注入容器
、註解
、AOP 面向切面程式設計
、基於 PSR-15 的中介軟體
、自定義程式
、基於 PSR-14 的事件管理器
、Redis/RabbitMQ 訊息佇列
、自動模型快取
、基於 PSR-16 的快取
、Crontab 秒級定時任務
、Session
、i18n 國際化
、Validation 表單驗證
等非常便捷的功能,滿足豐富的技術場景和業務場景,開箱即用。
本作品採用《CC 協議》,轉載必須註明作者和本文連結