Laravel - Одновременные обновления AJAX вызывают состояние гонки на модели - PullRequest
0 голосов
/ 14 мая 2019

В следующих примерах я приведу фиктивный код, поскольку не хочу показывать слишком много ненужных деталей.

Итак, у меня есть таблица базы данных, например:

Schema::create('settings', function (Blueprint $table) {
    $table->increments('id');
    $table->json('settings');
    $table->timestamps();
});

Поскольку settings - это JSON, я хотел бы иметь возможность частично обновить его с помощью моего REST-контроллера, например, /api/settings/setting_group.sub_setting?v=new value

. Вышеприведенное будет видоизменяться примерно так: $setting->settings["sub_setting"] = $new_value;

Я использую это для хранения своих переводов, поэтому отправлю их одновременно, когда клиент браузера что-то изменил.en.links - это просто путь в settings JSON.

  • /api/settings/en.links
  • /api/settings/zh-HK.links

Эта проблема не возникала, когдаv - простая строка, я предполагаю, что это потому, что Laravel работает быстрее, если v не является большим массивом PHP, поэтому быстрая синхронизация уменьшила проблему.Но состояние гонки произошло, когда v было большим.

Это данные формы, которые у меня были, когда это произошло.Для удобства чтения это без кодировки URL.

/api/settings/en.links?
v[0][text]=en link a&
v[0][url]=http://link.a&
v[1][text]=en link b&
v[1][url]=http://link.b&
api_token=xxx

/api/settings/zh-HK.links?
same thing but in zh-HK translation

Это способ обработки ввода в моем контроллере:

public function update(Request $request, $setting)
{
    $user = Auth::user();
    $sectioned_setting = Setting::findOrFail($user->section_id);

    // ...validator logic...

    $form_v = $request->input("v")?:"";

    // This line duplicates $setting->settings then make the changes in that clone
    // and set the clone back into the Setting model
    $v = $sectioned_setting->setValue($setting, $form_v);

    $sectioned_setting->save();

    return response()->json([
        "v" => $sectioned_setting->settings  // This line used to just return $v
    ], Response::HTTP_ACCEPTED);
}

Вот в основном то, что я получил в Chrome NetworkНа вкладке, если я обновил оба текста ссылки в en и zh-HK одновременно:

/api/settings/en.links

{
    en: {
        links: [
            {
                text: "xxxUPDATED",
                url: "xxx"
            },
        ]
    },
    zh-HK: {
        links: [
            {
                text: "xxx",
                url: "xxx"
            },
        ]
    }
}


/api/settings/zh-HK.links

{
    en: {
        links: [
            {
                text: "xxx",
                url: "xxx"
            },
        ]
    },
    zh-HK: {
        links: [
            {
                text: "xxxUPDATED",
                url: "xxx"
            },
        ]
    }
}

Как видите, мутация zh-HK в поле модели перезаписала переменную en.Так что это должно быть проблемой гонки.Мне нужно, чтобы весь процесс обновления и сохранения был атомарной операцией.

Есть ли способ решить эту проблему?Как можно защитить сервер?


РЕДАКТИРОВАТЬ:

После прочтения я попробовал пессимистическую блокировку для выбора и обновления, но она также не работала.(Это было сделано для части моих тестов. Поэтому я считаю, что это не работает, или это сработало для немногих случайно. Хотя я не знаю, почему несколько раз это сработало.)

Я заменил:

$sectioned_setting = Setting::findOrFail($user->section_id);

до

$sectioned_setting = Setting::whereId($user->section_id)->limit(1)->lockForUpdate()->first();

Возможно, я думаю, потому что запросы используют разные соединения с базой данных, поэтому блокировки не сохраняются.

Я предполагаю, что возможноможно добавить столбец version в таблицу и вручную выполнить атомарную операцию для этого.(Поэтому он проверяет прямо перед сохранением, если обновленная модель имеет ту же версию, повторите операцию, если она отличается.) Но Я хотел бы знать, есть ли способ в Laravel сделать это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...