2011-01-17 2 views
0

Я унаследовал приложение, которое делает следующий вид запроса в большом количестве мест:Почему SQL-запросы с функцией VBA работают так медленно?

select foo.f1, foo.f2, foo.f3 
from foo 
where foo.f4 = getFooF4() 

getFooF4 выглядит следующим образом

Public Function getFooF4() 
Dim dbCurrent As Database 
Dim rstBar As Recordset 

    Set dbCurrent = CurrentDb 
    Set rstBar = dbCurrent.OpenRecordset("Bar", _ 
              dbOpenDynaset, _ 
              dbSeeChanges) 
    getFooF4 = rstBar![myF4] 
    ''yes this appears broken... Bar only contains one row :-/ 

    rstBar.close 
    Set rstBar = Nothing 
    dbCurrent.close 
    Set dbCurrent = Nothing  
End Function 
'' Note: in my experimentation getFooF4 only runs once during the 
''  execution of the query. 

Это заканчивается работает довольно медленно. Если удалить getFooF4() из запроса с константой:

select foo.f1, foo.f2, foo.f3 
from foo 
where foo.f4 = 123456 

или параметр:

select foo.f1, foo.f2, foo.f3 
from foo 
where foo.f4 = [myFooF4] 

или с объединением:

select foo.f1, foo.f2, foo.f3 
from foo 
INNER JOIN bar ON bar.myF4 = foo.f4 

Он работает гораздо быстрее.

Почему?

функции: App написана и работает в MS Access 2003, фоновым базы данных SQL Server 2008.

+0

Я также отмечу, что запросы статически определяются как объекты запроса в MS Access, а не на стороне SQL Server. – BIBD

+0

Посмотрите, насколько чистый и простой запрос 'INNER JOIN'. Неужели вы не видите, как заставить конкретный план (например, требуя второго подключения для поиска значения в таблице) не позволяет оптимизатору выполнять свою работу? – onedaywhen

+0

@ODW - Да, на самом деле я переключил большинство из них на использование запроса INNER JOIN. Есть пара, которой я еще не могу по некоторым другим причинам WTF. Меня больше интересует ПОЧЕМУ исходный код настолько медленный. В настоящее время это похоже на эффект неявного типа Variant, возвращаемого функцией. – BIBD

ответ

2

Две вещи, чтобы улучшить эффективность (хотя только один или другой будет когда-либо применить к конкретному случаю, как это):

  1. определить тип возвращаемого значения для функции, то есть, Public Function getFooF4() должен Public Function getFooF4() As Long (или независимо от типа данных). Без явного типа данных он возвращает вариант. На самом деле никогда не существует функции VBA, которая никогда не будет иметь декларации типа возврата - если она возвращает вариант (который отлично разумно, особенно когда вам нужно вернуть Null в некоторых случаях), определите его с помощью As Variant. s некоторый другой тип данных, явным образом определяю его.

  2. объявить параметр в вашем SQL, чтобы оптимизатор запросов мог использовать эту информацию при расчете плана запроса. Это не применяется, если ваш ИНЕКЕ использует функцию для обеспечения критерия, но если вы используете ссылку на поле в элементе управления, вы бы заменить это:

.

select foo.f1, foo.f2, foo.f3 
    from foo 
    where foo.f4 = Forms!MyForm!MyControl 

...с этим:

PARAMETERS [Forms]![MyForm]![MyControl] Long; 
    select foo.f1, foo.f2, foo.f3 
    from foo 
    where foo.f4 = Forms!MyForm!MyControl 

Теперь, в любом из этих случаев, так как функция/параметр в ИНЕКЕ, она должна быть решена только один раз, так что даже если функция является неэффективной (как в данном случае , при этом он инициализирует переменную базы данных и открывает набор записей), на самом деле это не имеет большого значения.

Еще одна вещь, которую стоит рассмотреть - это заменить функцию простым DLookup(), который предназначен именно для этой цели. С другой стороны, так как значение приходит из таблицы, вы должны быть в состоянии присоединиться к его к одной строке таблице:

select foo.f1, foo.f2, foo.f3 
    from foo INNER JOIN Bar ON foo.f4 = Bar.MyF4 

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

+0

Ничего себе !!! Добавление «как долго» делало это намного быстрее! Это потому, что запрос не оптимизируется из-за неявного типа Variant для этой функции или заставляет его загружать больше данных, как предлагал iDevlop? – BIBD

+0

Я думаю, что это из-за оптимизации. Я не знаю, приведет ли к отсутствию оптимизации оптимизация всей таблицы - я думаю, что это не так, но, возможно, Jet/ACE принимает другое решение о том, что отправить на сервер на основе типа данных , –

+0

Вы можете протестировать, запустив SHOWPLAN on и сравните различные версии, без типа возврата, с возвращаемым типом Variant и с Long (или любым другим типом соответствующего сильного типа данных) и посмотрите, каковы различия. –

1

И как его можно сравнить с:

r = getFooF4() 

select foo.f1, foo.f2, foo.f3 
from foo 
where foo.f4 = r 

Если это так медленно, как оригинал , тогда ответ прост: функция getFooF4() - это медленная часть.

+0

Как именно вы выполнили бы то, что вы только что предложили в Access? И знаете ли вы, что оптимизатор запросов Jet/ACE уже сделает это (т. Е. Он будет выполнять эту функцию только один раз)? –

4

Ваш образец с GetFooF4 не может быть оптимизирован ни сервером Sql, ни через Access. И возобновление этого rs все время очень неэффективно. Как правило, избегайте использования специальных функций или кода Access в ваших запросах. Это предотвращает Acces от отправки запроса «как есть» на сервер Sql. Вместо этого он должен загружать всю совокупность данных и обрабатывать их локально, что означает увеличение трафика и меньшую скорость.
См http://msdn.microsoft.com/en-us/library/bb188204(v=sql.90).aspx#optaccsql_topic2

+0

Я не думаю, что это правда в этом случае. Оптимизатор запросов Jet/ACE разрешит значение, возвращаемое функцией, и отправит на сервер только буквальное значение. Jet/ACE вытащил бы всю таблицу, если функция возвращала другое значение для каждой строки, но это не так, так что этого не произойдет. –

Смежные вопросы