1. SQL / Говнокод #14412

    −138

    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    ALTER TABLE `test` ENGINE MyISAM;
      SELECT COUNT(*) FROM `test`;
      ALTER IGNORE TABLE `test` ADD UNIQUE INDEX `dupidx` (`col1`, `col2`, ...);
      SELECT COUNT(*) FROM `test`;
      ALTER TABLE `test` DROP INDEX `dupidx`;
    ALTER TABLE `test` ENGINE InnoDB;

    На Говнокод не тянет. Но идея мне кажется говнистой. Задача удалить все записи с дубликатами значений в полях.

    Запостил: Vasiliy, 24 Января 2014

    Комментарии (58) RSS

    • Нечто подобное - применение уникального индекса для удаления дубликатов - мне встречалось книге одного из именитых мэтров SQL.
      Так что, сама идея нормальная. А вот конкретная реализация может попахивать (я не работал с упомянутой ДБ).
      Ответить
      • > Так что, сама идея нормальная.
        Да не особо она нормальная. Адекватный и предсказуемый результат будет только если этот уникальный индекс строить по всем подряд колонкам. В остальных случаях это какой-то корейский рандом.
        Ответить
    • Блеать, mysql еще и грохает рандомную запись из совпавших во время построения unique index'а?!

      Ну все, теперь я точно никогда не буду с ней связываться.
      Ответить
      • Так IGNORE включает такое опасное поведение. Иначе будет аборт.
        Ответить
        • В InnoDB IGNORE не работает, и это хорошо :)

          UPD: Лол, так это баг! http://bugs.mysql.com/bug.php?id=40344
          Ответить
          • Самое смешное, что в 2008-м году они обещали этот баг пофиксить в версии 6.0. Сон разума рождает чудовища...
            Ответить
            • Но потом они опомнились и решили его никогда не фиксить?
              Ответить
              • Нет, потом они опомнились и решили никогда не выпускать MySQL 6.0.
                Ответить
                • P.S. Оказывается, на Рутрекере есть всё, даже MySQL 6.0. На официальном сайте нет, а на Рутрекере есть. Пираты совсем совесть потеряли.
                  Ответить
                  • > Пираты совсем совесть потеряли.
                    Ну дык опенсурс же, форкнули и дописали ;)
                    Ответить
                • Версии 6 - прокляты. Perl, PHP, MySQL. Кресты это обошли, сразу с 03 на 11
                  Ответить
                  • Borland C++ Builder 6.0
                    Visual C++ 6.0
                    Ответить
                    • И где они сейчас?
                      Ответить
                      • Юзаются, до сих пор ;)

                        Как минимум знаю 2 конторы, где юзают BCB 6. А визуалку юзает Тарас.
                        Ответить
                        • когда речь идёт о 6й вижуалке, то ещё очень спорно, кто кого юзает.

                          на самом деле всем известно, что Тарас обожает всё новенькое
                          поэтому не надо грязи, он юзает 2003 студию
                          Ответить
                    • DOS опять же
                      Ответить
                      • И тоже, сука, никак не умрет, по крайней мере проги под него. Шестые версии или не выходят вообще, или их труп гальванизируют десятилетиями...

                        P.S. Самый популярный FoxPro не шестой случаем был?
                        Ответить
                  • Windows 6.0 проклята. А по версии некоторых - и 6.2 вместе с ней
                    Ответить
                  • > Версии 6 - прокляты
                    На то они и шестерки, что не любят их...
                    Ответить
      • Слава богу, что это говнорасширение IGNORE работает только в myisam, и в InnoDB не поддерживается.
        Ответить
        • Именно поэтому первой строчкой ALTER TABLE `test` ENGINE MyISAM;
          а последней обратно ALTER TABLE `test` ENGINE InnoDB;
          Ответить
          • Меня мучает вопрос, сколько времени будет тарахтеть, если скормить этому алгоритму, например, базу ФИАС.
            Ответить
            • Сейчас скачаем да затестим ;)
              Ответить
            • P.S. В мускуле можно замутить отдельный тейблспейс для InnoDB базы? А то у меня в корне всего гигов 6 свободно, а чистить влом.
              Ответить
              • Есть вариант:
                1. С помощью innodb_data_home_dir создать новый корень InnoDB.
                2. Параметр конфига innodb_file_per_table раскидает таблицы по файлам, как в MyISAM.
                Ответить
                • Спасибо, работает.

                  P.S. Сраный фаерфокс не напомнил о недокачанном файле при выходе, и я качаю фиас заново ;(
                  Ответить
            • Кстати, база ФИАС - мелкая херня по сравнению с выгрузкой openstreetmap.
              Ответить
            • Ну что, 30кк записей из таблицы HOUSE залиты (ФИАС что-то быстро растет, в старом снепшоте было 20кк). Запускаем алгоритм Василия ;)
              Ответить
              • > алгоритм Василия
                и в данном случае использование этого словосочетания столь же правомерно, как и "болезнь Альцгеймера" или "палочка Коха"
                Ответить
              • mysql> ALTER TABLE `house` ENGINE MyISAM;
                Query OK, 29939779 rows affected (1 min 7.18 sec)
                Records: 29939779  Duplicates: 0  Warnings: 0
                
                mysql> SELECT COUNT(*) FROM `house`;
                +----------+
                | COUNT(*) |
                +----------+
                | 29939779 |
                +----------+
                1 row in set (0.01 sec)
                
                mysql> ALTER IGNORE TABLE `house` ADD UNIQUE INDEX `dupidx` (`aoguid`);
                Query OK, 29939779 rows affected (1 min 58.11 sec)
                Records: 29939779  Duplicates: 29276795  Warnings: 0
                
                mysql> SELECT COUNT(*) FROM `house`;
                +----------+
                | COUNT(*) |
                +----------+
                |   662984 |
                +----------+
                1 row in set (0.00 sec)
                
                mysql> ALTER TABLE `house` DROP INDEX `dupidx`;
                Query OK, 662984 rows affected (0.50 sec)
                Records: 662984  Duplicates: 0  Warnings: 0
                
                mysql> ALTER TABLE `house` ENGINE InnoDB;
                Query OK, 662984 rows affected (25.20 sec)
                Records: 662984  Duplicates: 0  Warnings: 0
                Шустрый алгоритм.
                Ответить
                • > шустрый
                  > ~211 секунд
                  а в сравнении с нормальным алгоритмом на нормальной субд?
                  Ответить
                  • Мне сейчас лениво заливать эту хрень еще час в другую субд ;) Завтра попробую на слонёнке.

                    > нормальным алгоритмом
                    Что-то типа такого?
                    delete from house using house ref
                        where house.aoguid = ref.aoguid and
                              house.updatedate < ref.updatedate;
                    Ответить
                    • странный запрос (как и непривычный синтаксис)
                      что будет с постгресом, если внутренний запрос для одной исходной строки сделает много результирующих строк?
                      Ответить
                      • > странный запрос
                        Ну в данном случае он бессмысленный (равно как и применение алгоритма Василия к этой базе), но идея примерно такая: из записей с совпадающим id удалить старые. На маленькой тестовой табличке работает.

                        > что будет с постгресом, если внутренний запрос для одной исходной строки сделает много результирующих строк?
                        Если имелось в виду select (select ...) as a from ... или select ... from ... where (select ...) < 42, где внутренний (select...) вернул несколько строк - то зафейлится.
                        Ответить
                      • Гугл подсказывает еще вот такой прием:
                        insert into result select h1.*
                        from house h1 left join house h2 on h1.aoguid = h2.aoguid
                            and h1.udpatedate < h2.updatedate
                        where h2.aoguid is null;
                        Ответить
                    • Удалять - это вряд ли очень бысто будет... лучше копировать в другую таблицу, и потом переименовать.
                      Ответить
                      • Да тут еще от задачи сильно зависит. Может быть эти старые записи вообще сгруппировать придется, сложив какие-нибудь суммы из них. Может быть придется выбирать какую из них грохнуть по какому-то условию, а часть вообще добивать руками. А может быть надо заодно поубивать что-то в других таблицах...

                        А в виде "перегрохать рандомные записи с совпадающими полями" (что и делает ignore в mysql) оно мало кому нужно. Я даже не могу придумать применений, ну кроме удаления полностью совпадающих строк.
                        Ответить
                        • ынтерпрайз намекает, что применения бывают
                          но, слава богу, не в mysql
                          Ответить
                          • > применения бывают
                            Например? Я просто в работе с базами не особо силен, поэтому и не могу придумать.
                            Ответить
                            • в прошлом году пришлось решать эту задачу
                              на таблице в десятки миллионов записей

                              в связи с багой closed source системы кое-какая статистика складировалась в эту самую таблицу с заметным дублированием каждой записи от 2 до 9 раз
                              причем, каждый дубликат, понятное дело, получал свой уникальный первичный ключ - но все остальные колонки были идентичны

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

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

                              p.s. ну а в этом году надо бы уже поработать с производителем и посмотреть, что они там наисправляли по этой проблеме
                              Ответить
                              • >>на таблице в десятки миллионов записей
                                Идентичная ситуация была. Не один раз причем. Один раз какая-то сука дропнула констрейнт, а потом другая (м.б. та же) написала говно.
                                Это практически неизбежная ситуация, как и то что каждый должен сделать update/delete без where.

                                Одна большая транзакция залочила бы всю таблицу и надолго. Шансов отработать - ноль. Сделал скрипт, который брал небольшими батчами, искал и удолял. За ночь всё отработало. Десятки миллионов в принципе немного.
                                Зато потом запросы на таблице стали летать!

                                >>теперь еженощно за 3 предыдущих дня (с запасом) статистика перепроверяется и дубликаты аккуратно чистятся
                                А не проще ли констрейнт повесить? Кто не пишет if exists () update <...> else insert <...> - тот сам виноват.
                                Ответить
                                • констрейнт будет означать неисправимую ошибку бд для активной высоконагруженной системы, которую разработал не ты и => исправить её в их говнокоде ты не можешь

                                  эффект будет непредсказуем, вплоть то до того, что она перестанет любую статистику собирать или вообще перестанет работать

                                  зачем мне такой праздник, у меня и так головной боли хватает
                                  Ответить
                                • > if exists () update <...> else insert <...>
                                  ну merge же
                                  не пугай меня на ночь своими конструкциями
                                  Ответить
                                  • >>ну merge же
                                    Мне показалось речь шла о каком-то жутком легаси.

                                    >>исправить её в их говнокоде ты не можешь
                                    Ну тогда костыль по планировщику, да - единственный выход.
                                    Ответить
                                  • > merge
                                    Жаль, что его в постгрес никак не впилят, вельми полезная штука.
                                    Ответить
                  • fias=> insert into result select h1.* from house h1 left join house h2 on h1.aoguid = h2.aoguid and h1.houseid < h2.houseid where h2.aoguid is null;
                    INSERT 0 662984
                    Time: 142904,246 ms
                    fias=> select count(*) from result;
                     count  
                    --------
                     662984
                    (1 row)
                    
                    Time: 259,014 ms
                    143 секунды.

                    Если есть какие-то предложения - можно затестить, базу я пока не грохнул.
                    Ответить
                    • погоди, засовывать в другую таблицу результат - неспортивно

                      или в майскл при смене джвижка примерно то же самое происходит?
                      Ответить
                      • > или в майскл при смене джвижка примерно то же самое происходит?
                        Да, там при смене движка оно копируется в другой файл.

                        Сейчас копию базы запилю, чтобы не жалко было, и опробую на ней что-нибудь с delete.
                        Ответить
                        • ну если так, то с delete будет заметно дольше

                          тебе же надо пару десятков М записей пометить, сегмент отката набить
                          не. совсем другую цифру получишь
                          Ответить
                          • Общество защиты баз данных меня побьет за жестокое обращение со слоненком:
                            fias=> delete from house where exists(select houseid from house h2 where house.aoguid = h2.aoguid and house.houseid < h2.houseid);
                            DELETE 29276795
                            Time: 1465177,637 ms
                            fias=> select count(*) from house;
                             count  
                            --------
                             662984
                            (1 row)
                            Ответить
                            • И еще 200 секунд слоник бегал с пылесосом:
                              fias=> VACUUM house;
                              VACUUM
                              Time: 198992,379 ms
                              Ответить
                          • > пришлось разбираться в ситуации и исправлять прямо в базе
                            > вручную за весь исторический период
                            А ты эту операцию делал через delete или переписывание в новую таблицу?
                            Ответить
                            • через delete

                              во-первых, там не 95% подлежало удалению, а около половины

                              ну и я вспомнил, что речь именно там шла не о десятках М, а о единицах

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

                              ну и в-третьих, там вполне производительный сервак, напрягся только на минуту, я гораздо дольше вылизывал этот запрос, чтобы ничего лишнего не угробить
                              Ответить
                              • Ну вот сейчас попробовал - напихал в таблицу 4кк записей, повторяющихся по 2 раза, и поудалял повторы самым тупым и брутальным способом:
                                fias=> insert into test(id, field1, field2) select id, md5((id / 2) :: varchar(32)), md5((id/2 + 1) :: varchar(32)) from generate_series(1, 4000000) as id;
                                INSERT 0 4000000
                                Time: 21861,741 ms
                                fias=> delete from test where exists(select id from test t2 where test.field1 = t2.field1 and test.field2 = t2.field2 and test.id < t2.id);
                                DELETE 1999999
                                Time: 55293,478 ms
                                fias=> select count(*) from test;
                                  count  
                                ---------
                                 2000001
                                (1 row)
                                
                                Time: 3709,793 ms
                                За минуту управилось. Т.е. постгрес не такой уж и медленный ;)
                                Ответить
    • twttr.receiveCount({"count":0,"url":"htt p:\/\/govnokod.ru\/14407\/"});
      Ответить

    Добавить комментарий