powered by simpleCommunicator - 2.0.59     © 2025 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / C++ [игнор отключен] [закрыт для гостей] / Си + ODBC + mssql
16 сообщений из 16, страница 1 из 1
Си + ODBC + mssql
    #38507660
Хто
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Здравствуйте!

Разбираюсь с работой с mssql-сервером. Конкретно -сейчас стоит 12-ый.

Пытаюсь работать через ODBC, но, видимо, не умею его готовить.

Просто выполнение INSERT 100 000 раз подряд занимает аж 68 секунд. Это же ненормально, да? Как бы это дело ускорить?

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
#include <windows.h>
#include <sqltypes.h>
#include <sql.h>
#include <sqlext.h>
#include <stdio.h>

#define TEST_ROW_COUNT 100000
#define COMMAND_MAX_SIZE 500

void HandleDiagnosticRecord(SQLHANDLE hHandle, 
							SQLSMALLINT hType,
							RETCODE RetCode)
{
	SQLSMALLINT iRec = 0;
	SQLINTEGER  iError;
	SQLWCHAR       wszMessage[1000];
	SQLWCHAR       wszState[SQL_SQLSTATE_SIZE+1];

	if (RetCode == SQL_INVALID_HANDLE){
		printf("Invalid handle!\n");
		return;
	}

	while ( SQLGetDiagRec(hType,
		hHandle,
		++iRec,
		wszState,
		&iError,
		wszMessage,
		(SQLSMALLINT)(sizeof(wszMessage) / sizeof(SQLWCHAR)),
		(SQLSMALLINT *)NULL ) 
		== SQL_SUCCESS )
	{
		if (wcsncmp(wszState, L"01004", 5))
		{
			wprintf(L"[%5.5s] %s (%d)\n", wszState, wszMessage, iError);
		}
	}
}



int main(int argc, char *argv[])
{
	SQLHSTMT stmt;
	SQLHENV env;
	SQLHDBC dbc;
	RETCODE retcode;
	clock_t time;
	SQLWCHAR command[COMMAND_MAX_SIZE];
	int i;

	// настраиваем всякую ODBC-шную дрянь

	// Allocate an environment handle 
	SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
	// We want ODBC 3 support 
	SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
	// Allocate a connection handle 
	SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);

	// Connect to the DSN 
	SQLDriverConnect(dbc, GetDesktopWindow(), NULL, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);

	// Check for success 
	printf("Check sql connection:\n");

	// проверяем подключением

	if(SQL_SUCCESS!=SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt))	{
		printf("SQL connection failed!\n");
	}
	else{
		printf("SQL connection Success!\n");

		// дропаем тестовую таблицу

		wsprintf(command, L"DROP TABLE TEST;");
		retcode = SQLExecDirect(stmt,command, SQL_NTS);
		if (retcode != SQL_SUCCESS)
		HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, retcode);

		// создаём опять тестовую таблицу

		wsprintf(command, L"CREATE TABLE TEST ( field1 int, field2 int, field3 int );");
		executeDirectCommand(command);
		retcode = SQLExecDirect(stmt,command, SQL_NTS);
		if (retcode != SQL_SUCCESS)
		HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, retcode);

		// команда для INSERT-а

		wsprintf(command, L"INSERT INTO dbo.TEST VALUES ( 1, 2, 3 );");
		time = clock();
		for(i=0; i<(uint32_t)TEST_ROW_COUNT; i++){

			// крутим команду 100 000 раз

			retcode = SQLExecDirect(stmt,command, SQL_NTS);
			if (retcode != SQL_SUCCESS)
			HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, retcode);
		}
		time = clock() - time;
		printf("Execution time = %4.1f", (float)time/CLOCKS_PER_SEC);
	}
	retcode = SQLFreeStmt(stmt, SQL_DROP);
	if (retcode != SQL_SUCCESS)
		HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, retcode);
	retcode = SQLDisconnect(dbc);
	if (retcode != SQL_SUCCESS)
		HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, retcode);
	retcode = SQLFreeConnect(dbc);
	if (retcode != SQL_SUCCESS)
		HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, retcode);
	retcode = SQLFreeEnv(env);
	if (retcode != SQL_SUCCESS)
		HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, retcode);

	getchar();
}
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38507682
Хто
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Так... Попробовал напрямую бросить бедному серверу через Management Studio 100 000 INSERT-ов. Пыхтел 25 секунд.

Куда же делись 40 секунд при работе через ODBC ?
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38507687
Хто
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Приближаемся к успеху.

Нашёл такую полезную штуку, как SQLBindParameter. Через него вставка 100 000 строк прошла за 19 секунд. Уже лучше. Но ведь можно же ожидать и меньшего времени, не так ли?

К примеру, bulkInsert 100 000 строк, выполненный непосредственно на сервере из файла занимает всего 0.5 секунды.

Нельзя приблизиться к этому результату как-нибудь?

Проблема в том, что я не могу использовать BULK INSERT - создать файл на стороне сервера не получится. А расшарить у себя, чтобы сервер смог его стащить - тоже не всегда. Хотелось бы какое-нибудь простое решение. Оно есть?

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
clock_t test2_BindParameters(void){
	clock_t time;
	SQLWCHAR command[COMMAND_MAX_SIZE];
	uint32_t *field1Rows;
	uint32_t *field2Rows;
	uint32_t *field3Rows;
	SQLUINTEGER paramProcessed;
	SQLUSMALLINT *ret;
	uint32_t i;
	RETCODE rc;

	field1Rows = (uint32_t*)malloc(TEST_ROW_COUNT*sizeof(uint32_t));
	if (field1Rows == NULL){
		printf("Malloc failed!\n");
		return 0;
	}
	field2Rows = (uint32_t*)malloc(TEST_ROW_COUNT*sizeof(uint32_t));
	if (field2Rows == NULL){
		printf("Malloc failed!\n");
		return 0;
	}
	field3Rows = (uint32_t*)malloc(TEST_ROW_COUNT*sizeof(uint32_t));
	if (field3Rows == NULL){
		printf("Malloc failed!\n");
		return 0;
	}
	ret = (SQLUSMALLINT*)malloc(TEST_ROW_COUNT*sizeof(uint32_t));	
	if (ret == NULL){
		printf("Malloc failed!\n");
		return 0;
	}

	for(i=0; i<(uint32_t)TEST_ROW_COUNT; i++){
		field1Rows[i] = i;
		field2Rows[i] = i;
		field3Rows[i] = i;
	}

	time = clock();

	rc = SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0);
	if (rc != SQL_SUCCESS) HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, rc);

	rc = SQLSetStmtAttr(stmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)TEST_ROW_COUNT, 0);
	if (rc != SQL_SUCCESS) HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, rc);

	rc = SQLSetStmtAttr(stmt, SQL_ATTR_PARAMS_PROCESSED_PTR, (SQLPOINTER)&paramProcessed, 0);
	if (rc != SQL_SUCCESS) HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, rc);

	rc = SQLSetStmtAttr(stmt, SQL_ATTR_PARAM_STATUS_PTR, (SQLPOINTER)ret, 0);
	if (rc != SQL_SUCCESS) HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, rc);

	rc = SQLBindParameter( stmt,
								   1,
								   SQL_PARAM_INPUT, 
								   SQL_C_SLONG, 
								   SQL_INTEGER,
								   NULL, 
								   0, 
								   field1Rows,
								   NULL, 
								   NULL
								   );
	if (rc != SQL_SUCCESS) HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, rc);

	rc = SQLBindParameter( stmt,
								   2,
								   SQL_PARAM_INPUT, 
								   SQL_C_SLONG, 
								   SQL_INTEGER,
								   NULL, 
								   0, 
								   field2Rows,
								   NULL, 
								   NULL
								   );
	if (rc != SQL_SUCCESS) HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, rc);

	rc = SQLBindParameter( stmt,
								   3,
								   SQL_PARAM_INPUT, 
								   SQL_C_SLONG, 
								   SQL_INTEGER,
								   NULL, 
								   0, 
								   field3Rows,
								   NULL, 
								   NULL
								   );
	if (rc != SQL_SUCCESS) HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, rc);

	
	wsprintf(command, L"INSERT INTO dbo.TEST VALUES (?, ?, ?);");

	//time = clock();

	rc = SQLExecDirect(stmt, command, SQL_NTS);
	if (rc != SQL_SUCCESS) HandleDiagnosticRecord(stmt, SQL_HANDLE_STMT, rc);

	time = clock()-time;

	free(field1Rows);
	free(field2Rows);
	free(field3Rows);
	free(ret);

	return time;
}
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38507803
Фотография Ggg_old
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
да тут латентность сети уже играет роль.
Есть много вариантов, но можно начинать с простейшего приема, который сразу даст заметный прирост при минимуме переделок: слать за вызов не единичный insert, а формировать и слать за один вызов сразу текст с несколькими inserta-ми. В этом случае можно даже без bind parameter.
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38507848
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ХтоНельзя приблизиться к этому результату как-нибудь?
К bulk insert на сервере приблизиться нельзя. Но можно отключить автокоммит и установить
SQL_ATTR_PARAMSET_SIZE если MS SQL его поддерживает.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38508046
Хто
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Ggg_oldда тут латентность сети уже играет роль.
Есть много вариантов, но можно начинать с простейшего приема, который сразу даст заметный прирост при минимуме переделок: слать за вызов не единичный insert, а формировать и слать за один вызов сразу текст с несколькими inserta-ми. В этом случае можно даже без bind parameter.

Да, сеть, конечно, влияет. Но я надеялся, что не столь фатально :). А несколько инсертов подряд - это мысль.

....

Хм... Попробовал так:
Код: plaintext
1.
wsprintf(command, L"INSERT INTO dbo.TEST VALUES ( 1, 2, 3 );INSERT INTO dbo.TEST VALUES ( 1, 2, 3 );");



И эту команду уже отправляю в цикле серверу.
Первый проход выполняется нормально, потом сервер выдаёт ошибку - Invalid cursor state.

База чистая - никаких триггеров и т.п. нет. Попытался нагуглить - не помогло. Не знаете, чем может быть вызвана такая ошибка?
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38508057
Хто
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Dimitry SibiryakovХтоНельзя приблизиться к этому результату как-нибудь?
К bulk insert на сервере приблизиться нельзя. Но можно отключить автокоммит и установить
SQL_ATTR_PARAMSET_SIZE если MS SQL его поддерживает.


Это понятно, что прям совсем приблизиться нельзя. Я имею ввиду - максимально ускорить процесс...

SQL_ATTR_PARAMSET_SIZE использую по дефолту (даже не знал, что его можно не задавать).

Автокоммит отключил.

При тупых инсертах время сократилось до 56 секунд против 68 (не так много, но неплохо).
При BindParameters получилось 5 секунд против 19 (уже радует).

Может, есть ещё какие плюшки?

Вы простите, если мои вопросы попахивают тупизной, но в вопросе работы с mssql я действительно нуб тридцатого уровня :(.
А объем статей на MSDN по этому поводу удручает. Пытаюсь, конечно, осилить. Но очень много тонкостей, видимо, ускользают.
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38508220
Фотография mayton
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Хто,

мне кажется этот вопрос более профильныый для форума MS-SQL. Есть много нюансов
в вопросе самого ODBC. Возможно он архитектурно не очень быстрый. И имеет смысл
посмотреть в сторону SQL Server Native Client. И задействовать всякие там bulk-функции
если есть возможность. Сетевой протокол покрутить. Выбрать TCP/NamedPipes и посмотреть
что будет лучше для вашей конфигурации.
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38508250
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ХтоЗдравствуйте!

Разбираюсь с работой с mssql-сервером. Конкретно -сейчас стоит 12-ый.

Пытаюсь работать через ODBC, но, видимо, не умею его готовить.

Просто выполнение INSERT 100 000 раз подряд занимает аж 68 секунд. Это же ненормально, да? Как бы это дело ускорить?


Смотри, делим 68 на 100000, получаем - 0,00068 сек на запись.

680 микросекунд.

И что же ты ещё-то хочешь ускорять ?

У тебя просто тупо много записей.
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38508252
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ggg_oldда тут латентность сети уже играет роль.
Есть много вариантов, но можно начинать с простейшего приема, который сразу даст заметный прирост при минимуме переделок: слать за вызов не единичный insert, а формировать и слать за один вызов сразу текст с несколькими inserta-ми. В этом случае можно даже без bind parameter.

Это делается в ODBC по-другому -- через array binds -- запрос один, но у него каждый параметр -- вектор значений.
Напр. в 1000.
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38508255
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ХтоDimitry Sibiryakovпропущено...

К bulk insert на сервере приблизиться нельзя. Но можно отключить автокоммит и установить
SQL_ATTR_PARAMSET_SIZE если MS SQL его поддерживает.


Это понятно, что прям совсем приблизиться нельзя. Я имею ввиду - максимально ускорить процесс...

SQL_ATTR_PARAMSET_SIZE использую по дефолту (даже не знал, что его можно не задавать).

Автокоммит отключил.

При тупых инсертах время сократилось до 56 секунд против 68 (не так много, но неплохо).
При BindParameters получилось 5 секунд против 19 (уже радует).

Может, есть ещё какие плюшки?

Вы простите, если мои вопросы попахивают тупизной, но в вопросе работы с mssql я действительно нуб тридцатого уровня :(.
А объем статей на MSDN по этому поводу удручает. Пытаюсь, конечно, осилить. Но очень много тонкостей, видимо, ускользают.


Ещё открою тебе тайну, что bulk insert есть и в ODBC.
Но ты сначала Попробуй array binds, параметр ты уже нашёл -- SQL_ATTR_PARAMSET_SIZE.
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38508339
Фотография Ggg_old
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
можно делать как masterziv описал, это уже более профессиональный подход, но завязанный на конкретные возможности сервера и драйвера.
Насчет идеи множетсва инсертов, то оно должно работать везде с любым сервером и любым клиентом. Почему у тебя выдается такая ошибка, надо смотреть. Приведи пожалуйста полный код.
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38508366
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38511288
Хто
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
MasterZivСмотри, делим 68 на 100000, получаем - 0,00068 сек на запись.
680 микросекунд.
И что же ты ещё-то хочешь ускорять ?
У тебя просто тупо много записей.

Проблема в том, что в реальной задаче строк ещё больше - порядка 50 миллионов. Вот и пытаюсь найти решение, которое позволит выполнить вставку в базу за обозримое время, а не за трое суток :).

MasterZivЭто делается в ODBC по-другому -- через array binds -- запрос один, но у него каждый параметр -- вектор значений.
Напр. в 1000.

Через Binding arrays of parameters я пытался - третий пост темы. Там используется SQLBindParameter. По сравнению с тупыми инсертами дало серьёзное увеличение скорости. Но всё равно - на больших объёмах данных работает удручающе медленно. Может, есть ещё какие-нибудь варианты биндинга?

MasterZivЕщё открою тебе тайну, что bulk insert есть и в ODBC.


Воооооооооот! Вот этого мне и не хватало.

Переделал с использованием bcp_init()...bcp_sendrow()...bcp_done().

Результат - 100К строк за 0.7 секунды были съедены базой.

Вот это уже результат! Напомню, bulk insert 100К строк из файла непосредственно на сервере занимал 0.5 секунды. Клёво!

Огромное вам спасибо за советы!

Делаю по схеме, представленной в этой статье:
http://technet.microsoft.com/en-us/library/ms403278.aspx

Подключил реальные данные - там для затравки всего 6 миллионов строк. При использовании такой схемы они улетают в базу за 250 секунд. Хм... Я ожидал что-то вроде 6 000 000/ 100 000 * 0.7 = 42 секунды.

Вызов bcp_sendrow() в цикле отъедает много времени.

В принципе, меня почти устраивает уже достигнутый результат. Но нельзя ли улучшить как-нибудь и его? Было бы круто, к примеру, иметь возможность отправлять серверу не по одной строке, а несколько за раз. Но почему-то подобного механизма я на MSDN не нашёл с использованием bcp.

В общем и целом, как и ожидалось, bcp даёт лучшие результаты. Поэтому остановлюсь на этом варианте. Попробую найти, как его ещё можно ускорить.
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38511384
Хто
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
ХтоПодключил реальные данные - там для затравки всего 6 миллионов строк. При использовании такой схемы они улетают в базу за 250 секунд.


Тут я несколько наврал. Там действительно 6 миллионов строк, но уже не int, а char * - каждое значение примерно по 20 байт.

Если сделать с int-ами, то результат - 6 миллионов строк (по три int-значения каждая) выгружаются в базу за 30 секунд.
...
Рейтинг: 0 / 0
Си + ODBC + mssql
    #38511506
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Хто,

там по идее еще настраивается, сколько сторону в одной пачке слать....
...
Рейтинг: 0 / 0
16 сообщений из 16, страница 1 из 1
Форумы / C++ [игнор отключен] [закрыт для гостей] / Си + ODBC + mssql
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]