2010-06-23 2 views
0

Учитывая этот дизайн таблицы в веб-приложении:Увеличение значения на новой строке

CREATE TABLE `invoice` (
    `invoice_nr` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `revision` int(10) unsigned NOT NULL DEFAULT '1', 
    PRIMARY KEY (`invoice_nr`,`revision`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish_ci 

Что наиболее удобный и надежный способ, чтобы вставить новую ревизию счета и получить обратно назначенный номер версии?

Первый очевидный подход поражает меня как ненадежные в общей среде:

SELECT MAX(revision)+1 AS next_revision -- E.g. 2 
FROM invoice 
WHERE invoice_nr = 31416; 

INSERT INTO invoice (invoice_nr, revision) 
VALUES (31416, 2); 

Этот вариант выглядит немного лучше (я не знаю, если это на самом деле лучше):

INSERT INTO invoice (invoice_nr, revision) 
SELECT 31416, MAX(revision)+1 
FROM invoice 
WHERE invoice_nr = 31416; 

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

SELECT MAX(revision) AS last_revision 
FROM invoice 
WHERE invoice_nr = 31416; 

Есть ли рекомендуемый метод?

Приложение работает на PHP с хорошим старым расширением mysql - mysql_query() et al.

+0

Я не совсем уверен, почему вы установили колонку invoice_nr быть AUTO_INCREMENT здесь при вставке нескольких записей (с разными версиями) против того же invoice_nr –

+0

@Mark Baker: Во-первых, потому что изменений не было в более ранних версиях. Во-вторых, поскольку я думал, что AUTO_INCREMENT все еще служит своей цели: новые счета-фактуры требуют нового номера. –

+0

Возможно, вам было лучше нормализовать таблицу счетов-фактур и таблицу invoice_revisions. –

ответ

0

Я собрал пару методик из руководства по MySQL. Мне показалось, что я должен поделиться им здесь для записей.

1. Таблица блокировки

Если вы поместите замок на столе, будут поставлены в очередь другие одновременные процессы.Тогда вам не нужен какой-либо специальный трюк, чтобы избежать простофили:

LOCK TABLES invoice WRITE; 

SELECT MAX(revision)+1 AS next_revision -- E.g. 2 
FROM invoice 
WHERE invoice_nr = 31416; 

INSERT INTO invoice (invoice_nr, revision) 
VALUES (31416, 2); 

-- Or INSERT INTO ... SELECT 

UNLOCK TABLES; 

2. LAST_INSERT_ID (выражение)

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

CREATE TABLE `sequence` (
    `last_value` INT(50) UNSIGNED NOT NULL DEFAULT '1' COMMENT 'Last value used' 
); 

START TRANSACTION; 

UPDATE sequence 
SET last_value=LAST_INSERT_ID(last_value+1); 

INSERT INTO invoice (invoice_nr, revision) 
VALUES (31416, LAST_INSERT_ID()); 

COMMIT; 
3

У Mysql есть функция, называемая last_insert_id(), которая возвращает последний автоматически сгенерированный идентификатор. Таким образом, вы можете просто SELECT last_insert_id() сразу после вставки ваших данных.

PHP имеет встроенную функцию для этого: mysql_insert_id().

Подробнее об этом здесь: http://www.php.net/manual/en/function.mysql-insert-id.php

Несмотря на то, что все это верно и в целом полезно это на самом деле не то, что разыскиваются здесь. Как бы это сделать, это генерировать таблицы. Один вызываемый счет-фактура с вами автоматически увеличивает поле, а второй называется invoice_revisions. Это должно иметь тот же формат, что и таблица счетов с добавленным полем обзора.

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

INSERT INTO invoice_revision SELECT i.*,IFNULL(max(ir.revision),0)+1 AS revision FROM invoice i LEFT JOIN invoice_revision ir on ir.invoice_nr = i.invoice_nr WHERE i.invoice_nr = ?

Затем обновите таблицу счета в обычном режиме. Таким образом, у вас есть ваши последние данные в таблице счетов и список всех предыдущих версий в таблице invoice_revisions.

Примечание, если вы используете таблицы MyISAM вы и установите версию на auto_increment в этой таблице:

http://dev.mysql.com/doc/refman/5.5/en/example-auto-increment.html

+0

Вы предлагаете добавить новый столбец идентификатора и удалить 'invoice_nr' и' revision' из первичного ключа? –

+0

Я обновил свой ответ, чтобы дать более подробный способ устранения проблемы. – Scimon

0

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

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

+0

Я все еще не очень хорошо знаком с транзакциями MySQL. Безопасны ли они для этой цели? –

+0

Я проводил некоторые дополнительные испытания. Транзакции, похоже, не являются правильным инструментом: у вас, вероятно, есть блокировка таблиц. Выдается запрос 'LOCK TABLES invoice WRITE', за которым следует' SELECT MAX (ревизия) +1 ... 'и' INSERT INTO ... ', чтобы предотвратить дублирование из-за одновременного доступа. –

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