2010-10-03 2 views
6

Просто для примера:Как MySQL управляет несколькими запросами от нескольких пользователей одновременно?

У меня есть скрипт PHP, который управляет голосами пользователей.

Когда пользователь голосует, скрипт делает запрос, чтобы проверить, проголосовал ли кто-либо за тот же ID/продукт. Если никто не голосовал, он делает другой запрос и вставляет идентификатор в общую таблицу голосов с идентификатором, а другой - для вставки данных в таблицу голосов пользователей. И подобное поведение повторяется в других сценариях.

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

Если да, то как я предотвращаю это?

Спасибо?

Важное примечание: Я использую MyISAM! Мой веб-хостинг не позволяет InnoDB.

+0

Это может помочь показать структуру вашей таблицы (имена таблиц и столбцов) – rojoca

+0

Я просто приведу пример, но чтобы помочь вам: в таблице продуктов есть идентификаторы productID, vote_count и total_votes_value. В таблице user_product_votes есть идентификатор пользователя, идентификатор productID и столбец user_vote. – Jonathan

+1

Единственная причина, по которой я спрашиваю, что в этом примере кажется, что было бы лучше реорганизовать вашу логику и структуру таблицы, чтобы одновременные вставки не имели значения, а не пытаясь предотвратить их. Вы задаете общий вопрос или вам нужно решить конкретную проблему в вашем примере? – rojoca

ответ

6

Вопрос заключается в том, если два разных пользователей голосов одновременно его возможно, что два экземпляра кода попытаться вставить новый идентификатор (или какой-либо подобный тип запроса), что даст ERRO

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

Вы можете решить это, я считаю, с применением некоторой блокировки; , например.если вам нужно добавить голос к продукту с идентификатором theProductId: (псевдокод)

START TRANSACTION; 
//lock on the row for our product id (assumes the product really exists) 
select 1 from products where id=theProductId for update; 
//assume the vote exist, and increment the no.of votes 
update votes set numberOfVotes = numberOfVotes + 1 where productId=theProductId ; 
//if the last update didn't affect any rows, the row didn't exist 
if(rowsAffected == 0) 
    insert into votes(numberOfVotes,productId) values(1,theProductId) 
//insert the new vote in the per user votes 
insert into user_votes(productId,userId) values(theProductId,theUserId); 
COMMIT; 

Некоторые подробнее here

MySQL предлагает другое решение, а также, которые могут быть применимы здесь, insert on duplicate

например вы могли бы просто сделать:

insert into votes(numberOfVotes,productId) values(1,theProductId) on duplicate key 
    update numberOfVotes = numberOfVotes + 1; 

Если ваши голоса таблица имеет уникальный ключ на колонке ID продукта, выше будет сделать вставку, если конкретный theProductId не существует, в противном случае он будет делать обновить, где он увеличивает столбцы numberOfVotes на 1

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

+0

Сделки существуют, чтобы решить эту проблему. Используя такие транзакции, вы гарантируете, что ни один запрос никогда не сможет увидеть промежуточное состояние в базе данных. – SingleNegationElimination

+0

+1 для 'на дубликат ключа'. Атомная операция> ручная блокировка. – egrunin

-4

Это, когда одноэлементный образец пригодится. Это гарантирует, что код выполняется только одним процессом в одно мгновение.

http://en.wikipedia.org/wiki/Singleton_pattern

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

Cheers.

+0

Плохая идея, и это не решит проблему одновременного доступа к БД в PHP. – nos

+1

Одноэлементный класс (даже в типичной среде Java) не решит проблему. Это просто означает, что могут быть только одни экземпляры, но к этому экземпляру можно обращаться несколькими потоками параллельно. –

+0

Спасибо за объяснение :) – enokd

1

Вопрос заключается в том, если два различных пользователей голосов одновременно его возможно, что два экземпляра кода пытается вставить новый идентификатор (или какой-то подобного типа запроса), который даст ошибку? ?

Да, вообще это возможно. Это пример очень распространенной проблемы в параллельных системах, называемой race condition.

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

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

0

Самый простой способ:

LOCK TABLES table1 WRITE, table2 WRITE, table3 WRITE 
-- check for record, insert if not exists, etc... 
UNLOCK TABLES 

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

Таблицы InnoDB предлагают транзакции, которые могут быть полезны и здесь. Другие уже прокомментировали это, поэтому я не буду вдаваться в подробности.

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

Смежные вопросы