[MYSQL] Взять уникальную строку и обновить - транзакция

myweb101

Client
Регистрация
29.04.2013
Сообщения
175
Благодарностей
30
Баллы
28
Есть таблица в Innodb с полями id (автоинкрементное), login, pass, used (значение 1 или 0)

Шаблон работает во многопотоке, нужно запустить шаблон с 10 потоками, взять уникальный логин и пароль, обновить поле, что он используется в данный момент.

Написал процедуру, но что-то меня смущает:
1. Нужно ли использовать autocommit в 0 а потом возвращать 1?
2. FOR UPDATE - уместно?
3. Все верно?

Протестировал на 10 потоках на 1000 строках из таблицы, вроде все время берет уникальные значения.

Код:
SET autocommit=0;
START TRANSACTION;
    SET @peremennaia:= (SELECT full_login FROM mail WHERE used = 0 ORDER BY RAND() LIMIT 1 FOR UPDATE);
    SELECT id, full_login, password FROM mail WHERE full_login = @peremennaia;
    UPDATE mail SET used = 1 WHERE full_login = @peremennaia;
COMMIT;
SET autocommit=1;
 

IgorSush

Client
Регистрация
11.02.2016
Сообщения
312
Благодарностей
108
Баллы
43
Я в такие дебри мускуля не лез.

Делаю по-другому:
В потоке генерирую случайное число,
В первом запросе апдэйчу необходимую строку
UPDATE mail SET used="$big_random" WHERE used IS NULL AND ....​
Во втором получаю ее
SELECT * FROM mail WHERE used="$big_random"​
И потом по окончании потока обнуляю
UPDATE mail SET used=NULL WHERE used="$big_random"
Согласен, целых 3 транзакции, но для небольших объемов вполне.
Косяков не наблюдал
 

Koqpe

Client
Регистрация
23.12.2014
Сообщения
1 100
Благодарностей
649
Баллы
113
Я эмулирую работу со списком, взяли первую строку " id (автоинкрементное), login, pass, id_time", удалили и добавили в конец списка.

Берем самый "старый" аккаунт:

SELECT * FROM Innodb ORDER BY id_time ASC LIMIT 1

После взятия обновляем время использования аккаунта в таблице, поле id_time:

UPDATE Innodb SET id_time=NOW() WHERE `id` = 'полученный id в первом запросе'
 
Последнее редактирование:

myweb101

Client
Регистрация
29.04.2013
Сообщения
175
Благодарностей
30
Баллы
28
К сожалению все ваши варианты не подходят, кроме списка, если только использовать его в одном проекте и я еще создавал отдельный проект, который выгружал этот список, а если проектов несколько, но с ним тоже иногда возникали непонятки, он иногда бывал недоступен ил др. ошибки. Бд с блокировкой строк работает лучше.
Создайте БД с 5к строками и запустите в 50 потоков, когда останется строк меньше, чем потоков, то без блокировки строк тут не обойтись, т.к. в один момент потоки могут взять одинаковые значения. Легко проверить, сделав запись полученных значений в файл, а потом посмотрев повторы. Чем больше поток или обращений к БД, тем больше вероятность получить дубль. Да такое возникает очень редко....но
Делала с блокировкой таблицы с myisam, тоже неплохо, но один раз не выполнилось и вся таблица была в режиме lock, пришлось использовать Innodb.
И как я понял из доков SET autocommit=1; тут не обязательно.
 

Koqpe

Client
Регистрация
23.12.2014
Сообщения
1 100
Благодарностей
649
Баллы
113
Вы ставите разные задачи в первом и втором Ваших постах.

Под Вашу первую задачу мой способ работы с таблицей MYSQL будет:
все время берет уникальные значения.
Во всяком случае меня он устраивает :-)
 
Последнее редактирование:

Koqpe

Client
Регистрация
23.12.2014
Сообщения
1 100
Благодарностей
649
Баллы
113
del
 
Последнее редактирование:

IgorSush

Client
Регистрация
11.02.2016
Сообщения
312
Благодарностей
108
Баллы
43
... т.к. в один момент потоки могут взять одинаковые значения. Легко проверить, сделав запись полученных значений в файл, а потом посмотрев повторы.
Как так? Ведь на время апдэйта таблица лочится средствами мускуля, а когда разлочивается, used взятой строки уже не NULL и для следующих апдэйт запросов эта строка "занята". Одновременный доступ исключен.
Второй транзакцией мы эту строку находим по $big_random, а если ее нет - результат пустой, тут тоже не вижу проблемы.

По этой схеме работал с нагрузкой гораздо серьезнее, чем в 50 потоков, монитор показывал 150+ запросов в секунду, косяков не наблюдал. Причем строки брал не рэндомом, а по порядку, отсортированными по timestamp
 
Последнее редактирование:

myweb101

Client
Регистрация
29.04.2013
Сообщения
175
Благодарностей
30
Баллы
28
Спасибо, да конечно с update я согласен, а вот с select можно одновременно получит одинаковые значения. Ну и так ни кто и не ответил на вопрос, транзакцию я верно написал или нет.
 

kfil

Client
Регистрация
09.09.2016
Сообщения
18
Благодарностей
16
Баллы
3
Есть таблица в Innodb с полями id (автоинкрементное), login, pass, used (значение 1 или 0)

Шаблон работает во многопотоке, нужно запустить шаблон с 10 потоками, взять уникальный логин и пароль, обновить поле, что он используется в данный момент.

Написал процедуру, но что-то меня смущает:
1. Нужно ли использовать autocommit в 0 а потом возвращать 1?
2. FOR UPDATE - уместно?
3. Все верно?

Протестировал на 10 потоках на 1000 строках из таблицы, вроде все время берет уникальные значения.

Код:
SET autocommit=0;
START TRANSACTION;
    SET @peremennaia:= (SELECT full_login FROM mail WHERE used = 0 ORDER BY RAND() LIMIT 1 FOR UPDATE);
    SELECT id, full_login, password FROM mail WHERE full_login = @peremennaia;
    UPDATE mail SET used = 1 WHERE full_login = @peremennaia;
COMMIT;
SET autocommit=1;

вот какое у меня родилось решение

Код:
start transaction;
lock tables baza write;
set @free_id=-1;
SELECT id from `baza` where used is null  limit 1 into @free_id;
UPDATE `baza` SET used = 1 WHERE used is null and id=@free_id;
select @free_id;
unlock tables;
commit;
транзакция возвращает id первой свободной записи , если же такие отсутствуют то -1

ps
в строку коннект нужно добавить allowuservariables=True
psps
без локов таблицы всё равно проходят параллельные запросы..
upd
можно обойтись без локов если в конце селекта написать for update тем самым заблокировав аналогичные запросы
окончательный вариант такой
Код:
start transaction;
set @free_id=-1;
SELECT id from `baza` where used is null  limit 1 into @free_id for update;
UPDATE `baza` SET used = 1 WHERE used is null and id=@free_id;
select @free_id;
commit;
 
Последнее редактирование:

one

Client
Регистрация
22.09.2015
Сообщения
6 792
Благодарностей
1 264
Баллы
113
А в чем особенность предложенных решений? Разве не достаточно брать ближайшую строку со значением 0 и менять его на 1 тем самым указать что данный аккаунт занят.
 

kfil

Client
Регистрация
09.09.2016
Сообщения
18
Благодарностей
16
Баллы
3
А в чем особенность предложенных решений? Разве не достаточно брать ближайшую строку со значением 0 и менять его на 1 тем самым указать что данный аккаунт занят.
особенность это то чтобы в многопотоке не пересекались значения (если просто использовать два запроса то между ними может вклинится запрос другого потока и как следствие в двух потоках будут аналогичные данные )
 
  • Спасибо
Реакции: one и Koqpe

progrlab

Client
Регистрация
07.02.2014
Сообщения
204
Благодарностей
82
Баллы
28
start transaction;
set @free_id=-1;
SELECT id from `baza` where used is null limit 1 into @free_id for update;
UPDATE `baza` SET used = 1 WHERE used is null and id=@free_id;
select @free_id;
commit;
1.А данный код вы как есть из кубика "Работа с Базами данных" запускали ?
2.Или оформляли в хранимую процедуру и потом уже ее использовали ?
3.Можно из экшена "Работа с Базами данных" вызвать хранимую процедуру или только с помощью C# ?
 

kfil

Client
Регистрация
09.09.2016
Сообщения
18
Благодарностей
16
Баллы
3
1.А данный код вы как есть из кубика "Работа с Базами данных" запускали ?
2.Или оформляли в хранимую процедуру и потом уже ее использовали ?
3.Можно из экшена "Работа с Базами данных" вызвать хранимую процедуру или только с помощью C# ?
Запускал с помощью С# в виде одного запроса, в результате он возвращал значение @free_id. Хранимые процедуры не использовал.
Вот пример запроса:
Код:
string query="start transaction; set @free_id=-1; SELECT id from `table` where used = 0  ORDER BY RAND() limit 1 into @free_id for update; UPDATE `table` SET used = 1 WHERE used = 0 and id=@free_id; select @free_id; commit;";
string rez=ZennoPoster.Db.ExecuteScalar(query,null,ZennoLab.InterfacesLibrary.Enums.Db.DbProvider.MySqlClient, project.Variables["SqlConnect"].Value);
Переменная rez будет содержать либо номер свободного id либо если все заняты -1.
Можно использовать также для выбора свободного id довольно сложные условия.
Такой код нормально работает в многопотоке.
 

progrlab

Client
Регистрация
07.02.2014
Сообщения
204
Благодарностей
82
Баллы
28
Спасибо за подсказки и идеи.
Сделал так :
1. на сервере БД MySQL - хранимая процедура.

Код:
PROCEDURE ok_database.accnt_id()
  MODIFIES SQL DATA
BEGIN
  set @rid = -1;
  START TRANSACTION;
  SELECT  row_id INTO @rid FROM accounts_table at WHERE at.`condition`='READY' ORDER BY at.time_id ASC LIMIT 1 FOR UPDATE;
  UPDATE accounts_table at set at.time_id=NOW(),  at.`condition`='WORK'
  WHERE at.row_id=@rid and at.`condition`='READY';
  SELECT @rid;
  COMMIT;
END
2. В Zennoposter - два кубика.
C#
Код:
string query = "call ok_database.accoutn_id();";
string res = ZennoPoster.Db.ExecuteScalar(query,null,ZennoLab.InterfacesLibrary.Enums.Db.DbProvider.MySqlClient,
                                        project.Variables["MySQLConnectString"].Value);
                                       
return res;
И кубик работы с БД
Код:
SELECT <тут перечисление полей>
FROM accounts_table
WHERE row_id=@A
ORDER BY time_id ASC LIMIT 1;
 
  • Спасибо
Реакции: eee, proffman и kfil

proffman

Client
Регистрация
24.01.2013
Сообщения
547
Благодарностей
51
Баллы
28
Я в такие дебри мускуля не лез.
Запускал в 600 потоков, часто информация либо теряется, либо дубли, либо и то и то. Из 1700к строк, бывало что 99 нехватило при удалении дублей. На 100 потоках парочку раз пробовал, более менее работает нормально. Но ИМХО, очень часто случаи, когда дубль недопустим.
Сделал так :
1. на сервере БД MySQL - хранимая процедура.
.....
2. В Zennoposter - два кубика.
.....
И кубик работы с БД
В MySql только начал разбираться, почти сутку сидел. Да, мощная это штука... С твоим кодом пока не пробовал, а хочется и его еще на тест поставить... Как у тебя ситуация сейчас с многопотоками, решил проблему с данной хранимой процедурой?
 
Последнее редактирование:

Gfoblin

Client
Регистрация
30.05.2013
Сообщения
4 512
Благодарностей
990
Баллы
113
Вот нашел пока такое
for update
Всё это работает только на таблицах типа InnoDB

Может кому поможет
 
  • Спасибо
Реакции: proffman

proffman

Client
Регистрация
24.01.2013
Сообщения
547
Благодарностей
51
Баллы
28

Gfoblin

Client
Регистрация
30.05.2013
Сообщения
4 512
Благодарностей
990
Баллы
113
  • Спасибо
Реакции: proffman

progrlab

Client
Регистрация
07.02.2014
Сообщения
204
Благодарностей
82
Баллы
28
Запускал в 600 потоков, часто информация либо теряется, либо дубли, либо и то и то. Из 1700к строк, бывало что 99 нехватило при удалении дублей. На 100 потоках парочку раз пробовал, более менее работает нормально. Но ИМХО, очень часто случаи, когда дубль недопустим.

В MySql только начал разбираться, почти сутку сидел. Да, мощная это штука... С твоим кодом пока не пробовал, а хочется и его еще на тест поставить... Как у тебя ситуация сейчас с многопотоками, решил проблему с данной хранимой процедурой?
Многопоток отрабатывает корректно. Можно с хранимой процедурой. Можно и без нее. Просто у меня привычка логику по максимому в БД выносить
Главное работать через транзакции и блокировки. SELECT FRO UPDATE, start transaction и т.д.
И действительно это работает не на всех движках. На InnoDB - работает.
 
  • Спасибо
Реакции: proffman и pym933

workoles

Client
Регистрация
02.05.2015
Сообщения
276
Благодарностей
81
Баллы
28
  • PROCEDURE ok_database.accnt_id()
  • MODIFIES SQL DATA
  • BEGIN
  • START TRANSACTION;
  • SELECT row_id INTO @rid FROM accounts_table at WHERE at.`condition`='READY' ORDER BY at.time_id ASC LIMIT 1 FOR UPDATE;
  • UPDATE accounts_table at set at.time_id=NOW(), at.`condition`='WORK'
  • WHERE at.row_id=@rid and at.`condition`='READY';
  • COMMIT;
  • END
Подскажи пожалуйста что в этом коде означают "at" и "at."...
не могу никак разобрать. Спасибо
 

doc

Client
Регистрация
30.03.2012
Сообщения
8 605
Благодарностей
4 596
Баллы
113
Подскажи пожалуйста что в этом коде означают "at" и "at."...
не могу никак разобрать. Спасибо
at - это псведоним accounts_table (имя произвольное)
at.имя_столбца - это тоже самое, что просто имя_столбца, но с явным указанием какой таблице он принадлежит. В основном эта явность необходима, когда запрос работает сразу не несколькими таблицами, что не перепутать принадлежность
 
  • Спасибо
Реакции: Koqpe, workoles и IgorSush

Кто просматривает тему: (Всего: 1, Пользователи: 0, Гости: 1)