Самый быстрый способ вставить в таблицу SQL Server из .NET-кода?
Каков самый быстрый способ сделать это:
- Одна таблица, никаких ссылок, которые я не могу предварительно заполнить (т.е. там есть один ссылочный ключ, но у меня есть все заполненные данные)
- Количество данных. Мы говорим о сотнях миллионов строк в день, динамически вступая через API
- Запросы должны/должны быть обработаны как можно скорее в сценарии почти реального времени (т.е. не выписывать файл для загрузки в день). 2 секунды - это нормальная максимальная задержка
- Отдельные машины для данных/приложений и SQL Server
Что я делаю сейчас:
- Совокупность до 32 * 1024 строк в массиве, а затем очередь.
- Прочитайте очередь в 2-3 потоках. Вставьте в базу данных с помощью SqlBulkCopy.
Я получаю около 60k-75k строк, импортированных в секунду, чего недостаточно, но довольно близко. Я хотел бы поразить 250 000 строк.
Пока ничего не используется. Я получаю 20-процентные блоки "сетевого ввода-вывода", имеют одну базовую 80-процентную загрузку процессора. Диски пишут 7mb-14mb, в основном бездействуют. Средняя длина очереди на RAID 10 из 6 raptors составляет.... 0.25.
Кто-нибудь знает, как ускорить это? Более быстрый сервер (до сих пор он виртуальный, 8 ГБ оперативной памяти, 4 ядра, физический диск для передачи данных).
Добавление некоторых пояснений:
- Это корпоративный SQL Server 2008 R2 на сервере 2008 R2. машина имеет 4 ядра, 8 гб баранов. Все 64 бит. 80% нагрузки приходится на эту машину, показывающую нагрузку около 20% cpu.
- Таблица проста, не имеет первичного ключа, только индекс реляционной ссылки (ссылка на инструмент) и уникальный (в пределах набора инструментов, поэтому это не соблюдается) отметка времени.
- Полями таблицы являются: отметка времени, ссылка на инструмент (без принудительного внешнего ключа), тип данных (char 1, одно из нескольких символов, указывающих, какие данные публикуются), цена (двойной) и объем (int). Как вы можете видеть, это ОЧЕНЬ тонкий стол. Данные, о которых идет речь, являются тиковыми данными для финансовых инструментов.
- Вопрос также в том, что касается аппаратного обеспечения и т.д. - в основном потому, что я не вижу реального узкого места. Я вставляю несколько транзакций, и это дает мне преимущество, но мало. Диски, CPU не показывают значительную нагрузку, сетевой io-ожидания высоки (300 мс/секунду, 30% на данный момент), но это на той же платформе виртуализации, которая запускает JSUT на двух серверах и имеет достаточно ядер для запуска всех. Я в значительной степени открыт для "покупки другого сервера", но сначала хочу определить узкое место... особенно учитывая, что в конце дня я не разбираюсь в узком месте. Ведение журнала не имеет значения - объемные вставки НЕ входят в журнал данных как данные (без кластеризованного индекса).
Помогло ли вертикальное разбиение на разделы, например, байтом (tinyint), который разделил бы юниверс инструмента, например, на 16 таблиц, и, таким образом, я сделал до 16 вставок одновременно? Поскольку на самом деле данные поступают из разных обменов, я могу сделать раздел на обмен. Это было бы естественным полем разделения (которое фактически является инструментом, но я мог бы дублировать эти данные здесь).
Еще несколько пояснений: скорость еще выше (90k), теперь явно ограничена сетью IO между машинами, что может быть переключением VM.
Теперь я делаю соединение на 32 тыс. строк, размещаю временную таблицу, вставляю в нее с помощью SqlBUlkdCopy, ТОЛЬКО использую ОДИН SQL-оператор для копирования в основную таблицу - сводит к минимуму любое время блокировки в главной таблице.
В большинстве случаев время ожидания остается в сети IO. Кажется, я сталкиваюсь с проблемами, когда VM мудрый. Переместится на физическое оборудование в следующие месяцы;)
Ответы
Ответ 1
Если вы управляете 70k строк в секунду, вам пока очень повезло. Но я подозреваю это, потому что у вас очень простая схема.
Я не могу поверить, что вы спрашиваете об этой нагрузке на
- виртуальный сервер
- одиночный массив
- Диски SATA
Сеть и ЦП совместно используются, IO ограничено: вы не можете использовать все ресурсы.
Любая статистика загрузки, которую вы видите, не очень полезна. Я подозреваю, что сетевая нагрузка, которую вы видите, представляет собой трафик между двумя виртуальными серверами, и вы будете привязаны к IO, если вы решите этот
Прежде чем продолжить, прочитайте 10 уроков из 35 тыс. т/с. Он не использовал виртуальную коробку.
Вот что я сделал бы, не предполагая возможности SAN и без DR, если вы хотите увеличить объемы.
- Купите 2 больших phyical сервера, RAM RAM типа unleveant, максимальная RAM, установите x64 install
- Диски + контроллеры = самые быстрые шпиндели, самый быстрый SCSI. Или отличное NAS-хранилище.
- 1000 МБ + сетевые адаптеры
- RAID 10 с диском 6-10 для файла один для только вашей базы данных
- Оставшийся диск RAID 5 или RAID 10 для файла данных
Для справки наша максимальная нагрузка составляет 12 миллионов строк в час (16 ядер, 16 ГБ, SAN, x64), но у нас есть сложность в нагрузке. Мы не в состоянии.
Ответ 2
Есть ли какие-либо индексы на столе, с которыми вы могли бы обойтись? EDIT: спрашивайте, когда вы печатали.
Можно ли превратить цену в целое число, а затем делить на 1000 или на какие-либо запросы?
Ответ 3
Вы пытались добавить pk в таблицу? Это улучшает скорость?
Существует также основанный на наборах способ использования таблиц таблиц для импорта данных csv из http://www.sqlservercentral.com/articles/T-SQL/62867/ (рядом с нижней, требуется бесплатная регистрация, но стоит того).
Вам может понравиться попробовать и проверить его производительность... с таблицей с таблицей с минимальной таблицей.
Ответ 4
Из ответов, которые я читаю здесь, кажется, что у вас действительно есть проблема с оборудованием, а не проблема с кодом. В идеале вы получите повышение производительности, предоставив больше дискового ввода-вывода или пропускной способности сети, или запустив программу на той же виртуальной машине, на которой размещена база данных.
Однако я хочу поделиться идеей о том, что вставки параметров таблицы действительно идеальны для большой передачи данных; хотя SqlBulkCopy выглядит так же быстро, что он значительно менее гибкий.
Я написал статью о теме здесь: http://www.altdevblogaday.com/2012/05/16/sql-server-high-performance-inserts/
Общий ответ: вы грубо хотите создать тип таблицы:
CREATE TYPE item_drop_bulk_table_rev4 AS TABLE (
item_id BIGINT,
monster_class_id INT,
zone_id INT,
xpos REAL,
ypos REAL,
kill_time datetime
)
Затем вы создаете хранимую процедуру для непосредственного копирования из параметра таблицы в фактическую таблицу, поэтому количество шагов между ними меньше:
CREATE PROCEDURE insert_item_drops_rev4
@mytable item_drop_bulk_table_rev4 READONLY
AS
INSERT INTO item_drops_rev4
(item_id, monster_class_id, zone_id, xpos, ypos, kill_time)
SELECT
item_id, monster_class_id, zone_id, xpos, ypos, kill_time
FROM
@mytable
Код SQL Server выглядит следующим образом:
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("item_id", typeof(Int64)));
dt.Columns.Add(new DataColumn("monster_class_id", typeof(int)));
dt.Columns.Add(new DataColumn("zone_id", typeof(int)));
dt.Columns.Add(new DataColumn("xpos", typeof(float)));
dt.Columns.Add(new DataColumn("ypos", typeof(float)));
dt.Columns.Add(new DataColumn("timestamp", typeof(DateTime)));
for (int i = 0; i < MY_INSERT_SIZE; i++) {
dt.Rows.Add(new object[] { item_id, monster_class_id, zone_id, xpos, ypos, DateTime.Now });
}
// Now we're going to do all the work with one connection!
using (SqlConnection conn = new SqlConnection(my_connection_string)) {
conn.Open();
using (SqlCommand cmd = new SqlCommand("insert_item_drops_rev4", conn)) {
cmd.CommandType = CommandType.StoredProcedure;
// Adding a "structured" parameter allows you to insert tons of data with low overhead
SqlParameter param = new SqlParameter("@mytable", SqlDbType.Structured);
param.Value = dt;
cmd.Parameters.Add(param);
cmd.ExecuteNonQuery();
}
}
Ответ 5
Все медленно.
Некоторое время назад мы решили аналогичную проблему (вставляем в DB десятки тысяч данных о ценах, так как я помню, что это было около 50K за период времени, и у нас было около 8 временных рамок, с которыми все столкнулись: 00, так что это было о 400K), и он работал очень быстро для нас (MS SQL 2005). Представьте, как он будет работать сегодня (SQL 2012):
<...init...>
if(bcp_init(m_hdbc, TableName, NULL, NULL, DB_IN) == FAIL)
return FALSE;
int col_number = 1;
// Bind columns
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.SymbolName, 0, 16, (LPCBYTE)"", 1, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Time, 0, 4, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Open, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.High, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Low, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Close, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;
if(bcp_bind(m_hdbc, (BYTE *)&m_sd.Volume, 0, 8, 0, 0, 0, col_number++) == FAIL) return FALSE;
<...save into sql...>
BOOL CSymbolStorage::Copy(SQL_SYMBOL_DATA *sd)
{
if(!m_bUseDB)
return TRUE;
memcpy(&m_sd, sd, sizeof(SQL_SYMBOL_DATA));
if(bcp_sendrow(m_hdbc) != SUCCEED)
return FALSE;
return TRUE;
}
Ответ 6
Не могли бы вы использовать горизонтальное разбиение?
См.: http://msdn.microsoft.com/en-us/library/ms178148.aspx и http://msdn.microsoft.com/en-us/library/ms188706.aspx
Вы также можете посмотреть на этот вопрос и, возможно, изменить модель восстановления:
Sql Server 2008 Настройка с большими транзакциями (700k + rows/transaction)
Некоторые вопросы:
Какую версию SQL Server вы используете?
Почему одно ядро на 80%? Это может быть узким местом, поэтому, вероятно, стоит что-то изучить.
Какую ОС вы используете, и это 64-бит?