2012-06-11 5 views
28

У меня есть таблица с более чем 100 миллионами строк в Innodb.Показатель счетчика Mysql на очень больших таблицах

Я должен знать, есть ли более 5000 строк, где внешний ключ = 1. Мне не нужен точный номер.

Я сделал некоторые испытания:

SELECT COUNT(*) FROM table WHERE fk = 1 => 16 секунд
SELECT COUNT(*) FROM table WHERE fk = 1 LIMIT 5000 => 16 секунд
SELECT primary FROM table WHERE fk = 1 => 0,6 секунды

у меня будет больше сеть и время обработки, но это может быть перегрузка 15,4 секунды!

У вас есть идея?

Благодаря

Edit: [соответствующие комментарии Добавлена ​​Op в]

я пытался ВЫБРАТЬ SQL_NO_CACHE COUNT (Fk) из таблицы, где Ф.К. = 1, но потребовалось 25 секунд

Mysql был настроен для Innodb с тюнером Mysql.

CREATE TABLE table (pk bigint(20) NOT NULL AUTO_INCREMENT, 
fk tinyint(3) unsigned DEFAULT '0', 
PRIMARY KEY (pk), KEY idx_fk (fk) USING BTREE) 
ENGINE=InnoDB AUTO_INCREMENT=100380914 DEFAULT CHARSET=latin1 

БД Материал:

'have_innodb', 'YES' 'ignore_builtin_innodb', 'OFF' 'innodb_adaptive_hash_index', 'ON'  
'innodb_additional_mem_pool_size', '20971520' 'innodb_autoextend_increment', '8' 
'innodb_autoinc_lock_mode', '1' 'innodb_buffer_pool_size', '25769803776' 
'innodb_checksums', 'ON' 'innodb_commit_concurrency', '0', 
'innodb_concurrency_tickets', '500' 'innodb_data_file_path', 
'ibdata1:10M:autoextend' 'innodb_data_home_dir', '', 'innodb_doublewrite', 'ON'  
'innodb_fast_shutdown', '1' 'innodb_file_io_threads', '4' 
'innodb_file_per_table', 'OFF', 'innodb_flush_log_at_trx_commit', '1' 
'innodb_flush_method', '' 'innodb_force_recovery', '0' 'innodb_lock_wait_timeout', '50' 
'innodb_locks_unsafe_for_binlog', 'OFF' 'innodb_log_buffer_size', '8388608' 
'innodb_log_file_size', '26214400' 'innodb_log_files_in_group', '2' 
'innodb_log_group_home_dir', './' 'innodb_max_dirty_pages_pct', '90'  
'innodb_max_purge_lag', '0' 'innodb_mirrored_log_groups', '1' 'innodb_open_files', 
'300' 'innodb_rollback_on_timeout', 'OFF' 'innodb_stats_on_metadata', 'ON' 
'innodb_support_xa', 'ON' 'innodb_sync_spin_loops', '20' 'innodb_table_locks', 'ON' 
'innodb_thread_concurrency', '8' 'innodb_thread_sleep_delay', '10000'  
'innodb_use_legacy_cardinality_algorithm', 'ON' 

Обновление '15: Я использовал тот же метод, до сих пор с 600 миллионами строк и 640 000 новых строк в день. Он все еще работает нормально.

+0

отсчет будет идти быстрее, если вы выбрали столбец в 'COUNT()', как таковой: 'SELECT COUNT (Fk) из таблицы, где взять Ф.К. = 1' – ClydeFrog

+0

посмотреть на этом сайте [] (HTTP : //www.mysqlperformanceblog.com/2007/04/10/count-vs-countcol/) для получения дополнительной информации – ClydeFrog

+4

@ ClydeFrog: Действительно? Согласно [руководству] (http://dev.mysql.com/doc/en/group-by-functions.html#function_count), '' COUNT (*) 'оптимизирован для возврата очень быстро, если' SELECT' извлекается из одной таблицы, никакие другие столбцы не извлекаются, и нет предложения WHERE. В самом деле, блог, на который вы ссылаетесь, указывает, что 'COUNT (*)' быстрее, чем 'COUNT (column)'. – eggyal

ответ

2

Наконец, самым быстрым был запрос первых X строк с использованием C# и подсчет номера строк.

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

SELECT pk FROM table WHERE fk = 1 LIMIT X 

я получил результат в 0,9 секунды.

Спасибо всем за ваши идеи!

+3

Я не вижу, как вы подсчитали число строк. Помните, добавив этот код? – nischayn22

+0

Мое приложение обрабатывает данные партиями. Количество времени между двумя партиями зависит от количества строк, которые должны быть обработаны – hotips

1

Если вы используете PHP, вы можете сделать mysql_num_rows на результат, который вы получили от SELECT primary FROM table WHERE fk = 1 => 0.6 seconds, я думаю, что это будет эффективно.

Но зависит от того, что на стороне сервера язык вы используете

+0

C# с последним официальным драйвером. Я думаю, что драйвер дает указатель на данные. Поэтому я могу иметь номер строки без необходимости извлекать весь набор данных. – hotips

+0

@ si2w Есть два способа получить данные с сервера: 'mysql_store_result()', где весь результирующий набор отправляется клиенту, и вы можете его подсчитать, и 'mysql_use_result()', где данные отправляются, если необходимо, но все данные должны быть получены до выдачи других команд. – glglgl

+0

Не удается подтвердить этот ответ. В моем случае 'COUNT()' длится 1.6s и с обычным SELECT 'mysql_num_rows' не возвращает данные 1.8s. – mgutt

16

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

SELECT 1 FROM table WHERE fk = 1 LIMIT 5000, 1 

Если строка возвращается, то есть 5000 и более записей. Я предполагаю, что столбец fk проиндексирован.

+0

Это интересно. Испытали ли вы такое решение - и хорошо ли оно работает? –

+1

@ypercube: Я проверил фиктивные данные с 3M строками, без индекса на fk и последовательно получил результаты в <1s (первый запуск был ~ 3s). Этот запрос сильно зависит от распределения данных, поэтому YMMV. –

18

Счетчик таблица или другой механизм кэширования решения:

InnoDB не сохраняет внутреннее количество строк в таблице, так как параллельные транзакции могут «видеть» разное количество строк в то же время. Чтобы обработать инструкцию SELECT COUNT (*) FROM t, InnoDB сканирует индекс таблицы, что занимает некоторое время, если индекс не полностью находится в пуле буферов. Если ваша таблица не меняется часто, использование кеша запросов MySQL является хорошим решением. Чтобы получить быстрый счет, вам нужно использовать таблицу счетчиков, которую вы создаете, и позволить вашему приложению обновлять ее в соответствии с вставками и удаляет ее. Если приблизительное количество строк достаточно, можно использовать SHOW TABLE STATUS. См. Section 14.3.14.1, “InnoDB Performance Tuning Tips”.

+0

У меня есть условие, когда условие => показать статус таблицы не поможет. У меня 400 000 новых строк каждый день ... Мне повезло! – hotips

+0

@ si2w Я не хотел советовать вам использовать 'SHOW TABLE STATUS'. Я рассказал о счетах и ​​кешировании. – scriptin

+0

Спасибо @ Дмитрий Scriptin! – hotips

0

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

SELECT 'X' 
FROM mytable 
WHERE myfield='A' 
HAVING COUNT(*) >5 

Это приведет к возврату одной строки или никакой строки вообще, в зависимости от условия выполнения.

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

+2

это медленнее, чем обычный счет ... Спасибо за идею! – hotips

+0

Благодарим вас за отправку результатов, @ si2w! Однако неутешительно, что движок MySQL не реализует эту (простую?) Оптимизацию. –

+0

Abviously not ... – hotips

2

Я должен добавить еще один ответ. У меня есть много исправлений/дополнений к комментариям и ответам.

Для MyISAM, SELECT COUNT(*) без WHERE считается мертвым - очень быстро. Все остальные ситуации (включая InnoDB в Вопросе) должны рассчитывать либо на BTree данных, либо на BTTI индекса, чтобы получить ответ. Поэтому нам нужно посмотреть, сколько можно пересчитать.

InnoDB кэширует данные и блоки индексов (по 16 КБ каждый). Но когда данные таблицы или индекс BTree больше, чем innodb_buffer_pool_size, вы гарантированно попадете на диск. Нажатие на диск почти всегда является самой медленной частью любого SQL.

Кэш запросов, когда он задействован, обычно вызывает время запроса около 1 миллисекунды; это, по-видимому, не проблема с какими-либо указанными таймингами. Поэтому я не буду останавливаться на этом.

... Но Runing запрос же дважды в ряд часто демонстрируют:

  • Первый запуск: 10 секунд
  • Второй запуск: 1 второй

Это является симптомом первого прогона, который должен извлекать большую часть блоков с диска, а второй нашел все в ОЗУ (buffer_pool). Я подозреваю, что некоторые из перечисленных таймингов являются фиктивными из-за того, что не реализовано проблема кеширования. (16 сек. Против 0,6 сек. может быть.)

Я буду нажимать на «образы дисков» или «блоки, которые необходимо трогать», как real метрикой, из которой SQL быстрее.

COUNT(x) проверяет x за IS NOT NULL перед подсчетом. Это добавляет крошечный объем обработки, но не меняет количество обращений к диску.

Предлагаемый стол имеет ПК и вторую колонку. Интересно, что это real таблица ?? Это делает разницу -

  • Если оптимизатор решает прочитать данные- то есть, сканирование в PRIMARY KEY порядке - это будет чтение данных ВТКЕЯ, который обычно (но не в этот хромой пример) намного шире, чем вторичный индекс БТР.
  • Если Оптимизатор решает прочитать вторичный индекс (но не нужно делать сортировку), будет меньше блоков для касания. Следовательно, быстрее.

Комментарии оригинальных запросов:

SELECT COUNT(*) FROM table WHERE fk = 1 => 16 seconds 
    -- INDEX(fk) is optimal, but see below 
SELECT COUNT(*) FROM table WHERE fk = 1 LIMIT 5000 => 16 seconds 
    -- the LIMIT does nothing, since there is only one row in the result 
SELECT primary FROM table WHERE fk = 1 => 0.6 seconds 
    -- Again INDEX(fk), but see below 

WHERE fk = 1 умоляет INDEX(fk, ...), предпочтительно только INDEX(fk). Обратите внимание, что в InnoDB каждый вторичный индекс содержит копию pk. То есть INDEX(fk) эффективно INDEX(fk, primary). Следовательно, третий запрос может использовать это как «покрытие» и не должен касаться данных.

Если таблица действительно является только двумя столбцами, то возможно вторичный индекс BTree будет более толстым, чем данные BTree. Но в реалистичных таблицах вторичный индекс будет меньше. Следовательно, сканирование индексов будет быстрее (меньше блоков для касания), чем сканирование таблицы.

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

innodb_buffer_pool_size = 25,769,803,776 Я бы предположил, что таблица и ее вторичный индекс (от FK) составляют примерно 3-4 ГБ. Итак, любой момент может быть Первый должен загружать много вещей. Затем a второй run будет полностью кэширован. (Конечно, я не знаю, сколько строк есть fk=1,? Предположительно меньше всех строк)

Но ... На 600M строк, таблица и индекс являются каждый приближается к buffer_pool 25 Гб , Таким образом, скоро может наступить день, когда он станет привязанным к I/O - это заставит вас вернуться к 16 (или 25) секундам; но вы не сможете. Затем мы поговорим об альтернативах выполнению COUNT.

SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000,1 - Давайте проанализируем это. Он будет сканировать индекс, но он остановится после 5000 строк. Из всего, что вам нужно, «больше 5K», это лучший способ получить его. Он будет последовательно быстрым (касаясь только десятка блоков), независимо от общего количества строк в таблице. (Он по-прежнему зависит от характеристик buffer_pool_size и кеширования системы. Но дюжина блоков занимает гораздо меньше секунды, даже с холодным кешем.)

Возможно, стоит обратить внимание на LIMIT ROWS_EXAMINED MariaDB. Без этого, вы могли бы сделать

SELECT COUNT(*) AS count_if_less_than_5K 
    FROM (SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000); 

Это может быть быстрее, чем доставка строки клиенту; он должен будет собирать строки внутри таблицы tmp, но доставлять только COUNT.

Примечание: 640K строк вставлены в день - это приближается к пределу для однострочного INSERTs в MySQL с вашими текущими настройками на жестком диске (не SDD). Если вам нужно обсудить потенциальную катастрофу, откройте другой вопрос.

Итог:

  • Будьте уверены, чтобы избежать кэш запросов. (используя SQL_NO_CACHE или выключив QC)
  • Запустить любой запрос синхронизации дважды; используйте второй раз.
  • Поймите структуру и размер задействованных BTree (s).
  • Не используйте COUNT(x), если вам не нужна нулевая проверка.
  • Не используйте интерфейс PHP mysql_*; переключиться на mysqli_* или PDO.
Смежные вопросы