Warning: Cannot use a scalar value as an array in /home/admin/public_html/forum/include/fm.class.php on line 757

Warning: Invalid argument supplied for foreach() in /home/admin/public_html/forum/include/fm.class.php on line 770
Форумы портала PHP.SU :: Версия для печати :: Оптимизация запроса UPDATE в MySQL
Форумы портала PHP.SU » PHP » SQL и Архитектура БД » Оптимизация запроса UPDATE в MySQL

Страниц (1): [1]
 

1. Phantik - 19 Августа, 2009 - 14:35:13 - перейти к сообщению
Сразу приношу извинения за большой текст. Задавал этот вопрос кратко на другом форуме, но людям не хватило информации, поэтому пришлось выложить более подробно:

Так устроен типичный запрос на поиск\проверку билета:
CODE (text):
скопировать код в буфер обмена
  1.  
  2. SELECT wins.LID,wins.DrawNum,wins.Module,wins.Tour,wins.TckNum,wins.PayDT,wins.WorkST,wins.CurTax,wins.FieldCont,
  3. summ.Money, summ.Descr1, summ.Descr2,
  4. mods.ModType, mods.LongName, mods.ShortName
  5. FROM wins  
  6. INNER JOIN summ ON (wins.LID=summ.LID AND wins.DrawNum=summ.DrawNum AND wins.Tour=summ.Tour)
  7. INNER JOIN mods ON (wins.LID=mods.LID AND wins.DrawNum=mods.DrawNum AND wins.Module=mods.Module)
  8. WHERE wins.LID=1 AND wins.DrawNum=532 AND wins.TckNum=358015"


Теперь описание таблиц участвующих в запросе.

Таблица wins. В ней хранятся все выигрыши. Количество записей всреднем 3-5 миллионов.
CODE (text):
скопировать код в буфер обмена
  1.  
  2. CREATE TABLE `wins` (
  3.   `LID` smallint(6) unsigned NOT NULL,                         // Код лотереи        
  4.   `DrawNum` smallint(6) unsigned NOT NULL,                // Номер тиража
  5.   `Module` smallint(6) unsigned NOT NULL,                    // Код типа розыгрыша
  6.   `Tour` smallint(6) unsigned NOT NULL,                        // Тур в котором выиграл билет
  7.   `TckNum` int(11) unsigned NOT NULL,                        // Номер билета
  8.   `PayDT` double DEFAULT 0.0,                                    // Дата\Время оплаты (если выигрыш был оплачен)
  9.   `WorkST` smallint(6) unsigned NOT NULL,                   // Номер рабочей станции оплатившей билет
  10.   `CurTax` smallint(6) unsigned NOT NULL,                    // Налог в % который был уплачен с выигрыша
  11.   `FieldCont` BLOB,                                                     // Игровое поле билета.
  12.     PRIMARY KEY(`LID`,`DrawNum`,`Module`,`Tour`,`TckNum`)
  13. ) ENGINE=MyISAM;


Таблица summ. В ней хранятся описания призов и денежные суммы призов. Записей в среднем 3-5 тысяч.
Связана с таблицей wins по полям LID, DrawNum, Module, Tour. Связь один(summ) ко многим(wins). Связь логическая, физически (средствами СУБД) не реализована.
CODE (text):
скопировать код в буфер обмена
  1.  
  2. CREATE TABLE `summ` (
  3.   `LID` smallint(6) unsigned NOT NULL,                            // Код лотереи
  4.   `DrawNum` smallint(6) unsigned NOT NULL,                   // Номер тиража
  5.   `Module` smallint(6) unsigned NOT NULL,                       // Код типа розыгрыша
  6.   `Tour` smallint(6) unsigned NOT NULL,                          //  Тур
  7.   `Money` bigint(20) unsigned NOT NULL,                         // Сумма
  8.   `Descr1` char(64)  DEFAULT NULL,                                // Текстовое описание приза
  9.   `Descr2` char(64)  DEFAULT NULL,                                // Текстовое описание приза №2
  10.    PRIMARY KEY(`LID`,`DrawNum`,`Module`,`Tour`)
  11. ) ENGINE=MyISAM;
  12.  


Таблица mods. В ней хранятся типы розыгрыша(особенности правил игры в определенном тираже)
Записей в среднем до 1000.
Связана с таблицей wins по полям LID,DrawNum,Module
CODE (text):
скопировать код в буфер обмена
  1.  
  2. CREATE TABLE `mods` (
  3.   `LID` smallint(6) unsigned NOT NULL,
  4.   `DrawNum` smallint(6) unsigned NOT NULL,
  5.   `Module` smallint(6) unsigned NOT NULL,
  6.   `ModType` smallint(6) unsigned NOT NULL,
  7.   `LongName` char(64)  DEFAULT NULL,
  8.   `ShortName` char(32)  DEFAULT NULL,
  9.   PRIMARY KEY (`LID`,`DrawNum`,`Module`)
  10. ) ENGINE=MyISAM;
  11.  


Так вот. Первый запрос выполняется приемлимо быстро. В среднем 1-50 мили сек. Особенности работы оператора состоят в том что если билет выигрышный(т.е. первый запрос вернулся не пустым), то ему надо зафиксировать оплату билета. Т.е. проапдейтить поля PayDT, WorkST, CurTax в таблице wins. Для этого выполняется следующий запрос:
CODE (text):
скопировать код в буфер обмена
  1.  
  2. UPDATE wins SET PayDT=40044.4448122338,WorkST=1,CurTax=0
  3. WHERE LID=1 AND DrawNum=532 AND TckNum=358015
  4.  


Так вот. ПРоблема в том что этот запрос уже выполняется по времени порядка 1 секунды. Что абсолютно неприемлимо ибо скорость оператора по вводу билетов может достигать 3-4 билетов в секунду. Отсюда следует собственно вопрос, как существенно ускорить запрос на изменение. Уверен что это возможно т.к. базы аналогичной структуры в СУБД Access и MSSQL выполняют такой запрос практически на порядок быстрее.

Теперь какие у меня есть мысли по всему этому.
1) Попробовать BLOB поле заменить на текстовое ибо тип хранимых данных это позволяет сделать.
2) С учетом пункта 1) (или без учета) вынести Blob(текстовое) поле в другую таблицу. Связь будет по LID,DrawNum,TckNum
3) Создать временную таблицу Tmp со структурой аналогичной таблице wins и все фиксации оплаты заносить туда с Помощью INSERT. (Протестил добавление - оно работает приемлемо быстро порядка 500 записей в секунду). И далее(например перед закрытием программы) либо писать хитрый запрос на вроде(проапдейтить в wins те записи которые присутствуют в Tmp). Такой запрос с помощью SQL вроде можно написать. Либо запускать параллельный поток, который будет брать запись из Tmp апдейтить соответствующую в wins и удалять из Tmp.
4) Мне кажется, что есть среди настроек MySQL те, которые отвечают за работу с кешем. Что бы апдейты записывались не напрямую в базу(А судя по времени, у меня возникает ощущение что СУБД пишет напрямую), а через кеш. Очень надеюсь что знающие люди подскажут эти настройки.
2. Champion - 19 Августа, 2009 - 14:39:55 - перейти к сообщению
Самым быстрым образом апдейт выполнится, когда есть индексы по тому, что участвует в WHERE и нет индексов по всему остальному. Первое гораздо значительнее. Выводы делайте сами.
(Добавление)
Phantik пишет:
1) Попробовать BLOB поле заменить на текстовое ибо тип хранимых данных это позволяет сделать
Обязательно.
(Добавление)
А вообще, написано очень много.
3. Stierus - 19 Августа, 2009 - 14:57:13 - перейти к сообщению
а экслэйн этого запроса что выдает?
4. Phantik - 19 Августа, 2009 - 15:30:39 - перейти к сообщению
Champion пишет:
Самым быстрым образом апдейт выполнится, когда есть индексы по тому, что участвует в WHERE и нет индексов по всему остальному. Первое гораздо значительнее. Выводы делайте сами.
(Добавление)
Phantik пишет:
1) Попробовать BLOB поле заменить на текстовое ибо тип хранимых данных это позволяет сделать
Обязательно.
(Добавление)
А вообще, написано очень много.


BLOB на char(n) заменил. Прирост получился несущественный.
Все поля которые в WHERE являются составным Primary Key т.е. я так понимаю индексированы автоматически. Поля которые обновляются - без индексов.

Есть аналогичная структура таблиц\запросов под MSSQL и MSAccess - запрос Update работает на порядок быстрее. Пытаюсь того же добиться на MySQL.
(Добавление)
Stierus пишет:
а экслэйн этого запроса что выдает?


id 1

select_type SIMPLE

table wins

type ref

possible_keys PRIMARY

key PRIMARY

key_len 4

ref const,const

rows 103610

Extra Using Where

Я правда не знаю какой из этого следует вывод Не понял
5. Champion - 19 Августа, 2009 - 15:57:33 - перейти к сообщению
Phantik пишет:
Все поля которые в WHERE являются составным Primary Key
А, значит обновляется всего одна запись. А селект с тем же условием быстро выполняется.
И еще. Этот апдейт не вызывает каскадных апдейтов, триггеров не запускает?
(Добавление)
А может индекс пересоздать?
6. Phantik - 19 Августа, 2009 - 17:48:54 - перейти к сообщению
Прошу всех прощения за то что из мухи сделал слона. Вообщем разобрался окончательно. Правильный ответ был:

alter table wins add index xxx (LID,DrawNum,TckNum);

И все работает мгновенно.

А до этого меня приглючило что будет достаточно только PRIMARY KEY(`LID`,`DrawNum`,`Module`,`Tour`,`TckNum`)
7. Champion - 19 Августа, 2009 - 18:03:26 - перейти к сообщению
Ну не знаю, как в MySQL, но обычно, если в запросе сравниваются на = часть полей составного ключа, он используется на ура.
(Добавление)
Обычно - это я про MSSQL, Access, Firebird, Interbase, Yaffil
8. EuGen - 19 Августа, 2009 - 18:06:23 - перейти к сообщению
Вот Вам вывод получается - индекс был неверен. Собственно, на них нужно было смотреть в первую очередь.
Про EXPLAIN рекомендую прочитать, полезная вещь. Так же как и про список процессов.
9. Phantik - 19 Августа, 2009 - 18:39:25 - перейти к сообщению
Champion пишет:
Ну не знаю, как в MySQL, но обычно, если в запросе сравниваются на = часть полей составного ключа, он используется на ура.
(Добавление)
Обычно - это я про MSSQL, Access, Firebird, Interbase, Yaffil


Я тоже с этим разобрался только сегодня. До этого такие же таблицы с такими же ключами в асексе и МС Скуэле работали мгновенно.

 

Powered by ExBB FM 1.0 RC1