Automatically retry non-atomic upsert operation when unique key constraints are violated.

Overview

Laravel Retry on Duplicate Key Build Status Coverage Status

Automatically retry non-atomic upsert operation when unique constraints are violated.

e.g. firstOrCreate() updateOrCreate() firstOrNew() + save()

Original Issue: Duplicate entries on updateOrCreate · Issue #19372 · laravel/framework

Requirements

Package Version Mandatory
PHP ^7.4 || ^8.0
Laravel ^6.0 || ^7.0 || ^8.0 || ^9.0
PHPStan >=1.1

Installing

composer require mpyw/laravel-retry-on-duplicate-key

Basic usage

The default implementation is provided by ConnectionServiceProvider, however, package discovery is not available. Be careful that you MUST register it in config/app.php by yourself.

<?php

return [

    /* ... */

    'providers' => [
        /* ... */

        Mpyw\LaravelRetryOnDuplicateKey\ConnectionServiceProvider::class,

        /* ... */
    ],

];
<?php

use Illuminate\Support\Facades\DB;

$user = DB::retryOnDuplicateKey(function () {
    // Email has a unique constraint
    return User::firstOrCreate(['email' => 'example.com'], ['name' => 'Example']);
});
OTHER YOU
SELECT
(No Results)
SELECT
(No Results)
INSERT
(OK)
INSERT
(Error! Duplicate entry)
Prepare for the next retry, referring to primary connection
SELECT
(1 Result)

Advanced Usage

You can extend Connection classes with RetriesOnDuplicateKey trait by yourself.

<?php

namespace App\Providers;

use App\Database\MySqlConnection;
use Illuminate\Database\Connection;
use Illuminate\Support\ServiceProvider;

class DatabaseServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        Connection::resolverFor('mysql', function (...$parameters) {
            return new MySqlConnection(...$parameters);
        });
    }
}
<?php

namespace App\Database;

use Illuminate\Database\Connection as BaseMySqlConnection;
use Mpyw\LaravelRetryOnDuplicateKey\RetriesOnDuplicateKey;

class MySqlConnection extends BaseMySqlConnection
{
    use RetriesOnDuplicateKey;
}

Differences from other native upsert implementations

These implementations are focused on atomically performing INSERT-or-UPDATE queries. Hence, there are definitely clear differences in usage.

  • firstOrCreate() updateOrCreate() save auto-increment numbering spaces efficiently.
    • In contrast, upsert() always increments the number even if no INSERT occurs and it leads to yield missing numbers. This can be a serious problem if the query is executed frequently. If you still want to go along with upsert(), you may need to consider using UUID or ULID instead of auto-increment.
  • firstOrCreate() has clear advantages if its call completes mostly with only one SELECT and rarely with succeeding one INSERT.
    • In contrast, you must always execute two queries in all cases with upsert().
  • As for updateOrCreate(), there may be extra considerations depending on RDBMS.
    • For RDBMS other than MySQL, updateOrCreate() would be better unless its call definitely changes field values on rows. upsert() may ruin the sticky optimization when the connection has both Reader (Replica) and Writer (Primary) because they assume that all rows narrowed by WHERE conditions have been affected.
    • In MySQL, upsert() has no limitations about that. It regards that only rows are affected whose field values are actually changed.
  • Be careful that upsert() never triggers Eloquent events such as created or updated because its implementation is on Eloquent Builder, not on Model.
  • Only upsert() supports bulk insert. It is beneficial if there are a large number of records and you don't need any Eloquent events.
You might also like...
PhpCodeAnalyzer scans codebase and analyzes which non-built-in php extensions used

PhpCodeAnalyzer PhpCodeAnalyzer finds usage of different non-built-in extensions in your php code. This tool helps you understand how transportable yo

Shortest Path - have a function ShortestPath (strArr) take strArr which will be an array of strings which models a non-looping Graph.

Have the function ShortestPath(strArr) take strArr which will be an array of strings which models a non-looping Graph

WARNING! This software is currently non-functional. - A system which makes installing Jexactyl far, far easier.
WARNING! This software is currently non-functional. - A system which makes installing Jexactyl far, far easier.

Jexactyl Assistant A system which makes installing Jexactyl far, far easier. WARNING 🚧 This software is currently in heavy alpha testing and WILL NOT

A tool to automatically fix Twig Coding Standards issues

Twig CS Fixer Installation This standard can be installed with the Composer dependency manager. Add the coding standard as a dependency of your projec

Helper to automatically load various Kirby extensions in a plugin

Autoloader for Kirby Helper to automatically load various Kirby extensions in a plugin Commerical Usage This package is free but if you use it in a co

Bind your interfaces to implementations automatically.
Bind your interfaces to implementations automatically.

Laravel Auto Binder This package automatically binds interfaces to implementations in the Service Container, scanning the specified project folders. T

Automatically delete old SiteTree page versions from Silverstripe

Version truncator for Silverstripe An extension for Silverstripe to automatically delete old versioned DataObject records from your database when a re

Automatically load the next page of products in Magento. Easy to install and configure, this module works 100% out of the box with vanilla Magento 1.9.x and earlier.

Automatically load the next page of products in Magento. Easy to install and configure, this module works 100% out of the box with vanilla Magento 1.9.x and earlier.

Magento 2 module to automatically flush the cache whenever you save something in the System Configuration

Yireo AutoFlushCache Magento 2 module to automatically flush the cache whenever you save something in the System Configuration. Do NOT use this in pro

Comments
  • [PoC] PDO::ATTR_ERRMODE is implicitly changed to PDO::ERRMODE_SILENT after PDO::lastInsertId() call fails

    [PoC] PDO::ATTR_ERRMODE is implicitly changed to PDO::ERRMODE_SILENT after PDO::lastInsertId() call fails

    Overview

    SQL Server handles auto-incrementing values differently than other RDBMS. I accidentally forgot to change the value of the $incrementing property of Model that is not auto-incremented. In this case, PDO::lastInsertId() is unexpectedly called and its call will fail. This is the intended design.

    However, I found that after PDO::lastInsertId() failed, PDO::ATTR_ERRMODE was implicitly changed to PDO::ERRMODE_EXCEPTION and never restored. I'm convinced that this is a bug, so I submit a pull request to the pdo_sqlsrv repository.

    Test Summary

    public function testInsertingWithImplicitId(): void
    {
        $this->assertSame(
            \PDO::ERRMODE_EXCEPTION,
            $this->db()->getPdo()->getAttribute(\PDO::ATTR_ERRMODE),
        );
    
        // "users" table has IDENTITY field
        $this->assertSame(1, User::query()->create()->getKey());
    
        $this->assertSame(
            \PDO::ERRMODE_EXCEPTION,
            $this->db()->getPdo()->getAttribute(\PDO::ATTR_ERRMODE),
        );
    }
    
    public function testInsertingWithExplicitId(): void
    {
        $this->assertSame(
            \PDO::ERRMODE_EXCEPTION,
            $this->db()->getPdo()->getAttribute(\PDO::ATTR_ERRMODE),
        );
    
        // "posts" table doesn't have IDENTIFY field, but Model has been misconfigured.
        $this->assertSame(0, Post::query()->create(['id' => 1])->getKey());
    
        // BUG!!!
        $this->assertSame(
            \PDO::ERRMODE_SILENT,
            $this->db()->getPdo()->getAttribute(\PDO::ATTR_ERRMODE),
        );
    }
    
    opened by mpyw 0
Owner
mpyw
mpyw
Tiny PHP library providing retry functionality with multiple backoff strategies and jitter support

JBZoo / Retry 4 retry strategies (plus the ability to use your own) Optional jitter / randomness to spread out retries and minimize collisions Wait ti

JBZoo Toolbox 6 Dec 19, 2022
🔍 Generates database queries based on one unique string

?? Laravel Search String Generates database queries based on one unique string using a simple and customizable syntax. Introduction Laravel Search Str

Loris Leiva 735 Dec 30, 2022
A tool for managing SSH key access to any number of servers.

Revons - SSH Key Authority Features Easily manage SSH key access for all accounts on your servers. Manage user access and server-to-server access rule

Revons Community 1 Mar 14, 2022
High-performance, low-memory-footprint, single-file embedded database for key/value storage

LDBA - a fast, pure PHP, key-value database. Information LDBA is a high-performance, low-memory-footprint, single-file embedded database for key/value

Simplito 12 Nov 13, 2022
Allows reflection of object attributes, including inherited and non-public ones

sebastian/object-reflector Allows reflection of object attributes, including inherited and non-public ones. Installation You can add this library as a

Sebastian Bergmann 6k Jan 4, 2023
A non-intrusive support form that can be displayed on any page

A non-intrusive support bubble that can be displayed on any page Using this package you can quickly add a chat bubble that opens a support form on any

Spatie 288 Dec 28, 2022
search non profitable charity or organization through api search

Non Profile Charity Search Search non profitable organization or get the details of an organization Installation Require the package using composer: c

Touhidur Rahman 5 Jan 20, 2022
PhpCodeAnalyzer scans codebase and analyzes which non-built-in php extensions used

PhpCodeAnalyzer PhpCodeAnalyzer finds usage of different non-built-in extensions in your php code. This tool helps you understand how transportable yo

Sergey 92 Oct 19, 2022
IgAnalyzer - Find non-followers and potential stalkers.

igAnalyzer Features Shows mutual followers, non-followers and non-followings via pie chart Shows monthly followers via area chart Shows followers via

LIII 6 Sep 9, 2022
Cbe frontauth - A Textpattern plugin to manage backend connections from frontend and protect content from non-logged users

cbe_frontauth This client-side plugin lets your users (or you) manage backend connection from frontend, i.e. connect and disconnect as they (you) woul

null 4 Jan 31, 2020