Разве так правильно?
5067
45
Разбираюсь в доставшемся наследстве на PHP... очень много такого:

if ( !is_null($publication) ) { return $publication; }
else { return null; }

$publication - сложный объект некоего класса... но разве нельзя было просто написать в одну строку:

return $publication;

???

и ещё:

будет ли выполняться такой код?

if ( strlen($this->$field_name) == "" ) {
...
}

имхо тут явная ошибка... но это базовый класс от которого отнаследовано почти треть кода...
tolstopuz
"if ( !is_null($publication) ) { return $publication; }
else { return null; }"

Не знаю PHP, но сдается мне, что там масло масляное написано...
Sadovnikov
С этим разобрался. Действительно так.

А вот как обрабатывается второй пример? Из "общих соображений" вроде как: "то что справа" приводится к типу слева... но это верно для присваиваний, потому как вопрос возникает в момент "куды это теперича деть?"...
то есть пустая строка будет приведена к числу, то есть будет число 0(ноль), а стало быть условие выполняется "в принципе верно".

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

или действует какое-то более общее правило: целые - в вещественные, числа в строки... и т.д. т.е. приводим к тому типу, значения которого покрывают значения приводимого типа... ?!?

Что верно в ПХП? Порядок преобразования типов конкретно для сравнений - не нашел. Может плохо искал?!?
tolstopuz
Главное правило программиста: работает - не трожь!
tolstopuz
Порядок преобразования типов конкретно для сравнений - не нашел.
Тут одним компактным местом документации не отделаться, похоже.
"В случае, если вы сравниваете целое со строкой, строка будет преобразована к числу" (тыц)
"Преобразование строк в числа ... Значение определяется по начальной части строки. Если строка начинается с верного числового значения, будет использовано это значение. Иначе значением будет 0" (тыц)
tolstopuz
По первому случаю - такое бывает когда дебажишь и тебе надо поставить брейкпоинт при определенном условии. Потом соответственно забывается почистить, вот так иногда возникают такие с виду странные конструкции.

По второму - если это работает, то походу чел пошутил так, но за такие шутки полагается ата-та:улыб:

З.Ы. пхп не знаю
KSergey
По первому: пусть работает. Этого "чуда" сплошь и рядом. Замаешься править...

По второму: вот и пытаюсь разобраться работает ли? Дело в том, что это базовый абстрактный класс перекрывающий Zend_Db_Table_Row в частности это кусок из добавленной валидации значений по признаку "not null"... от него много чего наследовано...

Спасибо за "тыц". Похоже таки работает. На всякий случай поправил.:улыб:

Ещё вопросик:

Как лучше:

1. if ( !is_null($data) ) {...}
или
2. if ( null === $data ) {...}

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

или в ПХП is_null() - не вызов функции?
tolstopuz
По второму: вот и пытаюсь разобраться работает ли?
Что мешает поставить натурный эксперимент?

1. if ( !is_null($data) ) {...}
2. if ( null === $data ) {...}
или в ПХП is_null() - не вызов функции?
А что, сравнение - это бесплатно? это тоже скорее всего будет вызов функции, учитывая, что переменные в ПХП - нетипизированы, а значит их как минимум надо привести к одному типу ну или для начала этот самый тип определить и сравнить (коль скоро три равно стоят).
Ну и потом, есть специальная функция проверки на null, документированная.
Зачем вы пытаетесь ее заменить "по своему разумению", из каких-то "общих соображений", так сказать? (при том, что ПХП, похоже, не знаете, как и я, кстати)

PS
Интересно, у вас действительно нет других задач, кроме как править работающий код "по своему разумению"? это просто вопрос, а вовсе не наезд, если что.
KSergey
К сожалению, это моя основная задача на сегодня. Вот и приходится по горячему и ПХП изучать, и Зенд, и править... отсюда и вопросы.
А писано оно, вроде как профи программистами, один из которых сейчас где-то в Москве, а другой тоже вроде как начальник ИТ отдела в крупной фирме... вот и спрашиваю, "правильно ли"...

Вот, кстати ещё вопросики:

метод класса getData($keys, $forform, $usecache); в своем теле имеет три генерации объекта:
1. $products = new PartProducts(); далее через 5 строк ещё
2. $productParts = new PartProducts(); и внутри условия ещё
3. $partProducts = new PartProducts();

После создания объекта типа "таблица" в ней ищется один и тот же объект (запись) по параметру $keys[0]...
вопросы:
1. Это приводит к трем обращениям в Мускуль, или запись в реальности у Зенда выбирается только один раз?

2. Почему не возникает ошибка? При вызове метода в параметре всегда передается ровно одно число...

Заменил три конструктора одним - вроде работает... а вот заменить обращение к элементу массива - боязно...

П.С. правил метод по другой причине - параметр $forform приводил к разной заполняемости кэша данными, что было одной из причин возникающих проблем... там в одной ветке стояла полная сборка данных, а в другой - нет. Попутно нашел, что при неполной сборке, динамическая подгрузка класса работает только для старой версии адреса. А для добавленного в октябре класса Pobox - фигвам.
...просто "случайно" наткнулся и "оно" вызвало вопросы...

П.П.С. попутно устранил обслуживание кэша (это был дополнительный селект в ряде методов!) - вроде стало "поживее".
tolstopuz
Ээээ. Я извиняюсь, что не по сути вопроса, а в чем конкретно состоит задача? Может в оптимизации кода? Так это нужно делать профайлером, иначе получается борьба с приведениями. .А в профайлере четко будет видно где пробки
Камон
А в профайлере кругом "попа". Примерно одинаковая: выборка данных в несильно большой базе в среднем 2-40сек., и зависит больше от нагрузки на сервер (много join и как следствие блокировок). И связано как раз с наличием, практически везде, повторных обращений по генерации вспомогательных объектов с доп.выборками... примерно в каждом методе раза по три... или вложенные вызовы методов по 3-6 раз за одним и тем же... Вот сейчас из-за этого переписал класс Сategories.php.

Так, например, данные по доступности ресурсов для пользователя (ключи доступа) в объекте пользователя дублируются трижды , причем все, в смысле каждому пользователю собирается массив всех ключей базы трижды... где "это" пока ещё не нашел... "Итого" объект user содержит более 30_000 элементов!

Ну или вот автор подсистемы "списки фирм" (задания манагерам) зачем-то спроектировал таблицы так, что чтобы показать остаток недораспределенного списка (сколько ещё свободных фирм) делает в запросе подсчет по каждой строке разницы между общим количеством и суммой (отдельный подзапрос!) рапределенных записей... вместо того, чтобы просто хранить это число и контролировать его изменение по необходимости... результат: загрузка страницы со списками у начальника отдела "грузится" от 2 до 8 минут!!! Ну "куда это годится"?

А тот автор, который уже в Мск - зачем-то создал Мускульную процедуру разбора json строки для реализации расширенного поиска по базе... Я вот первый раз увидел как на мускуле можно синтаксические анализаторы писать... Сколько времени может работать такой "поиск", думаю оцените самостоятельно.:улыб:

Щас, ещё сижу осваиваю JS... потому как на стороне клиента обработка страницы может занимать от 0,5 до 3 минут.:хммм:

Примерно уже прикинул, что если буду улучшать это чудо с той же скоростью как сейчас (много времени уходит на разбор "что это" - документация и комментарии отсутствуют как понятие), то потребуется около 2-х лет...:хммм:

П.С. все-таки, хотелось бы получать ответы на вопросы, а не "филосовствовать"...
tolstopuz
все понятно, что ничего не ясно:улыб:
Где-то кэш нужно пользовать, где-то redundant данные вместо постоянных вычислений, где-то зарефакторить все нафик.
Сочувствую вобщем. Будут вопросы по java обращайтесь:миг:
Камон
Спасибо. Помощь понадобится по js. Чистой java тут ещё нет практически.

По заданным вопросам разобрался сам:
1. Нифига, все три генерации объекта приводят к запросам в БД.
2. Ошибки не возникает, не знаю почему... возможно генериться какой-нибудь варнинг, но они похоже давятся...
tolstopuz
... слов нет... Модераторы, прошу снести три предудыщих сообщения. Запаковал оба файла. Кодировка utf-8...
Вторая попытка.

Ещё раз, интересует как полезность изменений в коде, так и полезность такого документирования... может существуют какие-то более принятые стандарты? Все вызовы просмотрены grep`ом и поправлены. Код пока работает нормально.
tolstopuz
жэсть....
откройте для себя doxygen например.
и не пишите комментарии на русском - НИКОГДА. ну или готовьтесь встретить в чужом коде комментарии на хинди/китайском.
Mad_Dollar
или готовьтесь встретить в чужом коде комментарии на хинди/китайском.
Ну я встречал, и чё? не умер. Поразительно, правда?
Mad_Dollar
Спасибо, посмотрю. Но, наскольк посмотрел аннтотацию - это система генерирования документации. Замечательно, но для быстрой правки кода, наличие комментов в самом коде (особенно большом и такого) намного производительнее. Поэтому комментарии, ПХП-док - давно и не мной придуманы.

Комментари - для наших простых русских парней. В первую очередь "для себя любимого"... нафиг мне самому себе писать на английском или китайском?:миг:По поводу полезности комментов "для себя" - спорить не буду. Вы может и способны помнить весь код объемом в 6.5 мегабайт, я вот - нет. А судя по повторным созданиям объектов и дубликатам кода - тот кто так считал - на практике оказалось, что тоже нифига не помнит.

А "по существу", что нибудь подскажете?

Меня интересует:
1. Правильность оформления ПХП-док. То, что в заголовке класса помещать перечень методов нет смысла - уже понял. Средства Eclipse позволяют обходится без этого.
2. "Стилевые и оптимизирующие" правки кода:
а) Устранение промежуточных возвратных объектов. То есть перенос в оператор return последнего оператора метода.
б) создание промежуточных методов, которые по-просту устранят повторы кода в нескольких местах. Здесь это метод _getUserKeys(). Как оказалось - он ваще не нужен. Все ключи можно достать из контроллера, они там уже приготовлены...
в) оптимизация запросов. Здесь фактически произведена замена обращений к NestedSet на выборку по полю parent, которое практически содержит прямую ссылку на родителя.
г) изменение схемы вызова методов? Которое привело здесь к тому что теперь фактически методы getCategoriesArray, getNamedArray (бывший getCategoriesArray2Select) и кусок кода из getForm - по сути теперь новый метод, через который они все работают.
ИМХО достоинства:
1. Собственно выборка данных теперь в одном месте (потенциально - уменьшение ошибок).
2. Новый метод - универсален и позволяет делать много новых выборок и индексаций массивов.
3. Это просто стало понятнее как работает, или нет?

г) Насколько второй текст лучше понимается чем первый?
д) Насколько второй текст быстрее (и/или) менее объемен по данным первого?

Небольшой итог: Время переработки кода в целом заняло около 16-и часов. Из них:
1. около 8-и часов - это разбор набора таблиц этой части базы, кода класса (что это?) и анализ мест вызова (зачем это?). Попутно исправлено 2 мелких ошибки в местах вызова и устранен не нужный класс Filter. Как потом оказалось неполностью. Объекты класса еще создаются в html коде...
2. около 5-и часов заняло изменение собственно кода методов и мест вызова, где потребовалось. Долго, потому что с ПХП - я ещё далеко не на "ты".... много приходится лазить по документации...
3. около 2-х часов заняло документирование.
4. отладка кода заняла ещё около часа. Заработало с третьего раза.
Ошибки: 1. неправильное указание параметров в одном из вызовов.
2. Обнаружены вызовы из html.
3. При правке getParentsItems - забыл, что надо таки возвращать массив а не Rowset.
KSergey
я и не говорил, что умрете.
tolstopuz
phpdoc vs doxygen
фильтр php для doxygen
а учитывая, что в проекте может использоватся не один яык (php) а несколько (+python/java/plsql/etc) то имхо это вообще единственная нормальная система документирования, позволяющая сгенерировать документацию для всего проекта.

3. Это просто стало понятнее как работает, или нет?
если честно - разницы почти нет. кстати, не всегда стоит писать "универсальные методы, позволяющие исключить дублирование кода" - восприниматся сложнее начинает.
Mad_Dollar
Остальное комментировать, как понимаю не будете.
Просто, поскольку ПХП не знаю, надеялся на более обширное обсуждение, в том числе и Ваше, как специалиста.

Очень хотелось понять почему оно именно так было написано, ведь проведенная правка в десятки раз снижает как нагрузку на сервер БД, так и уменьшает объем обрабатываемых массивов, а значит и повышает скорость выполнения ПХП части...

Все равно, спасибо и на этом.
tolstopuz
ну а что там комментировать?

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

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

оптимизация нужна там, где она нужна. если вы выиграли полсекунды в бд, вы их можете легко потерять в другом месте, когда ваши полсекунды выигрыша конечный пользователь не заметит - здесь не существует "серебряной пули" для того чтобы оценить эффективность абстрактной правки абстрактного куска пусть даже конкретной системы.
у вас есть ваша реальная нагрузка и реальные типовые способы использования вашего продукта - можно иметь синтетические перфоманс-тесты, результаты которых будут более менее реалистичными. и эффективность в конечном итоге определяется только ими (для пользователя).
опять же - нагрузка на БД снизилась - а что это дало? снизилась средняя нагрузка? вам она была необходима? оптимизация ради оптимизации - путь в никуда. конечный пользователь как заметит этот рефакторинг? если бд была не перегружена - снижение нагрузки не дает в общем случае ничего - ну кроме самоощущения "какой я молодец". то есть это хорошо, но для конечного пользователя - все равно, сколько у вас запросов выполняется при генерации какого-нибудь отчета. и как используются индексы, или насколько красива объектная модель. хуже того - если ваш выигрыш - это микросекунды, он может быть даже не заметен пользователю, потому что наверстано так, что браузер полторы-две секунды отрисовывает результат. да, нагрузка на бд может быть и снизилась на полпроцента, но если она была 30-40 процентов - это просто моральное самоудовлетворение. опять же - эти полпроцента можно получить вообще другими средствами, и гораздо проще - например увеличив кэш БД в ряде случаев(и другими средствами администрирования системы, без девелопмента).

опять же - рефакторинг и вынос каких-то кусов кода в отдельные методы - не всегда хорошо, потому что могут давать полпроцента к производительности, но значительно ухудшать читаемость и понимаемость кода.

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

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

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

если ваш рефакторинг ничего из этого не ставил себе целью - очень может быть что с технической точки зрения вы сделали прекрасную работу, которая никому не была нужна кроме вас.
Mad_Dollar
БД, на сегодня, перегружена выше всякой меры. Уже писал об этом: 30-40 юзверей валят 4-х ядерный сервер, потому как на получение одной странички делается до 100 запросов в БД. Время отклика на сегодня "в среднем" от 30 секунд до 5-и минут! Писал же.

Это - базовый класс (один из десятка), через который работает вся система публикаций... подача баннеров в СМИ, корректура, верстка печатных СМИ, отчеты о проделанной работе и т.д.

Рефакторинг дал:
1. одно обращение к БД на один объект класса. В частности метод getParentItems, getCategoriesNamedArray - вместо просмотра в цикле узлов NestedSet - просто делают один запрос соединяя таблицу класса саму с собой для получения названий групп категорий. Один запрос против десятка (в среднем) для обхода дерева. На одну страничку выдачи, генерилось до 5-и таких объектов. В среднем - 2-3. Теперь 1, и только там где надо 2 (список категорий для выбора и те категории, по которым есть публикации).

2. Снижен уровень вложенности вызовов, практически с 4-5-и до 2-х. Раньше (здесь не очень видно): вызов статметода класса Filter, создание объекта, обращение к методу... в нем - ещё 3 вложенных вызова до БД. Теперь: создание объекта - обращение к методу и в нем вызов универсального метода.

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

4. Добавлены комментарии. Если обратили внимание, то общий размер файла, практически остался прежним. Теперь комменты занимают около трети-четверти. Так вот, как понимаю, если добавилось столько комментов, то примерно столько же кода убрано, как дублирующего... Не думаю, что это повышало "понимание" раньше.

П.С. посмотрел doxigen. Спасибки, хорошая вещица, сильно поможет для генерации общей документации. Буду делать, потому как числа с 15 февраля планирую открыть общий доступ в наш СВН для привлечения фрилансеров...
но, к сожалению, программ, которые бы телепатически догадывались о назначении классов, параметров и целей их взаимосвязей с БД - ещё не придумали. Так что в "повседневной" правке - лично мне, врядли поможет. Без наличия комментов в коде, она мало чем помогает, а они отсутствуют как класс, уже писал об этом.

П.П.С. судя по отсутствию замечаний, с самим ПХП - всё нормально... а то ведь большая часть правок вносилась исключительно методами мат-оптимизации, копируя и перенося куски текста, плохо понимая синтаксис языка...

Профайлер на предмет "что это дало конкретно" ещё не смотрел, но ряд страниц теперь грузится практически мгновенно по ощущениям...:улыб:
tolstopuz
Уже писал об этом: 30-40 юзверей валят 4-х ядерный сервер, потому как на получение одной странички делается до 100 запросов в БД. Время отклика на сегодня "в среднем" от 30 секунд до 5-и минут!
ну я вам так скажу - нужно тюнить БД и рефакторить не пэхопэ, а схему данных... у меня селект на over 40M строк на восьми полноценных ядрах (ибо гипертрейдинг это не полноценное ядро) занимает 1-2 секунды.
смотрите локи, смотрите рекомендации mysqltuner.pl (http://mysqltuner.pl/mysqltuner.pl), в конце концов - очень вероятно возможно что стоит больше денормализовать БД и покрутить настройки.
опять же - у меня на одну страницу до пятисот запросов - ничего, 1-2 секунды в среднем, если не используются тяжелые таблицы с миллионами строк - менее секунды. это необходимые издержки объектно-ориентированного кода с объектно-ориентированными универсальными прослойками методов.
Снижен уровень вложенности вызовов, практически с 4-5-и до 2-х.
если вы используете транзакции для селектов это не даст выигрыша. ибо кэш запросов и транзакции, хотя все зависит от размера кэша.
но, к сожалению, программ, которые бы телепатически догадывались о назначении классов, параметров и целей их взаимосвязей с БД - ещё не придумали.
взаимосвязи с БД - да, а вот описание структуры классов, методов и параметров вызовов делается полное. И если не называть методы DoOne, DoTwo и не передавать им переменные A, B, C (что относится и к именам классов), то в принципе даже без комментариев документация генерится сносная - даже без docblock'а.
Профайлер на предмет "что это дало конкретно" ещё не смотрел, но ряд страниц теперь грузится практически мгновенно по ощущениям...
а это единственный показатель нужности =)
Mad_Dollar
опять же - у меня на одну страницу до пятисот запросов
:eek:
Я всегда любил любителей ООП над БД...
Mad_Dollar
это необходимые издержки объектно-ориентированного кода с объектно-ориентированными универсальными прослойками методов
... как-то далеко не уверен. ООП - в своей основе - это табличное программирование, которое всегда было самым компактным и самым быстрым... в мое время. Оно канечна, "всё течёт, всё изменяется", но не до такой же степени!:улыб:
нужно тюнить БД и рефакторить не пэхопэ, а схему данных
Этим тоже занимаюсь.
Так например вот это:

$select->from(array('cl' => $this->_name), array(
'id' => 'cl.id',
'name' => 'cl.name',
'created_at' => 'DATE_FORMAT(cl.created_at, \'%d.%m.%Y\')',
'companies_count' => 'COUNT(DISTINCT ccd.id)',
'begin_date' => 'ccf.begin_date',
'end_date' => 'ccf.end_date',
'not_distributed' => 'COUNT(DISTINCT ccd.id) - COUNT(DISTINCT ccp.id)',
'creator_name' => "CONCAT(IFNULL(p.lname, ''), ' ', IFNULL(p.fname, ''))",
'allow_edit' => 'NOT ISNULL(ccf.begin_date)',
'isowner' => 'IF(cl.created_by = ccf.owner, true, false)',
'ccf_id' => 'ccf.id'
))
->joinLeft(array('ccf' => 'clists_m_company_function'), 'ccf.list_id = cl.id', array())
->joinLeft(array('ccd' => 'clists_company_department'), 'ccd.clists_m_company_function_id = ccf.id', array())
->joinLeft(array('dw' => 'm_division_worker'), 'dw.m_company_function_id = ccf.m_company_function_id', array())
->joinLeft(array('cp' => 'm_company_people'), 'dw.m_company_people_id = cp.id', array())
->joinLeft(array('u' => 'usr_users'), 'u.`data_type` = \'m_company_people\' AND u.data_id = cp.id', array())
->joinLeft(array('u2' => 'usr_users'), 'u2.id = cl.created_by', array())
->joinLeft(array('cp2' => 'm_company_people'), 'u2.data_id = cp2.id AND u2.data_type = \'m_company_people\'', array())
->joinLeft(array('p' => 'm_people'), 'p.id = cp2.m_people_id', array())
->joinLeft(array('ccp' => 'clists_m_company_people'), 'ccp.clists_m_company_function_id = ccf.id', array())
->where('u.id = ?', $user);

выбиралось за время более 20-и минут, в зависимости от нагрузки на мускуль. Да ещё и неправильно делаются подсчеты. Так для списка из 2405 фирм, в результате показывается companies_count = 10848... и это при том, что в базовой таблице всего ... 12 списков.

Убрал ВСЕ вспомогательные таблицы, добавив всего 4 поля в таблицу фирм списка 'clists_m_company' (вместо clists_m_company_function и clists_m_company_departments): `department_id`, `manager_id`, `for_deps_at` и `for_mans_at`, опираясь на то, что фирма из списка, может быть назначена единовременно только одному менеджеру и дополнительное условие что менеджер может быть назначен только после назначения отделу... весь запрос превратился в простой подсчет количества записей с заданным и пустым manager_id и department_id с группировкой по заданному списку... время отклика всей страницы... менее 2 сек.

Итого, от системы списков осталась таблица имен списков и таблица фирм этих списков с текущим указанием какая - кому назначена... всё.:улыб:
Mad_Dollar
ну я вам так скажу - нужно тюнить БД и рефакторить не пэхопэ, а схему данных...
В общем случае - не соглашусь. Ибо как один дурак может задать столько вопросов, что на них не ответит и сотня мудрецов, так и одна дурацки написанная программа может загрузить на 100% любой сервер.
Anomander
ну дураки разные бывают, предполагается что "дураки" не "полные овощи", так как все-таки программистами писаны. да и мудрецы "подзаточены под дураков".

при объемах в 70 тысяч строк и селектах по нескольку секунд (с несколькими джойнами, но все-таки будем считать размер таблиц ДО 100 тысяч строк) рефакторить нужно именно схему данных, или я чего-то не понимаю?
Mad_Dollar
при объемах в 70 тысяч строк и селектах по нескольку секунд (с несколькими джойнами, но все-таки будем считать размер таблиц ДО 100 тысяч строк) рефакторить нужно именно схему данных, или я чего-то не понимаю?
Нужно смотреть и то, и другое. Я не раз сталкивался с ситуациями, когда программы
(не мои :)) прекрасно вели себя в разработке, но при выходе на рабочую нагрузку вешали все, что только можно. Причина - неоптимальная работа с базой данных. Если страница делает 100 запросов к серверу, с одной такой страницей он справится прекрасно, а вот когда клиентов будет не 1-2, а 100 или 1000, все становится уже не так весело. Можно конечно поставить 4-8 процессорный сервер или даже кластер для обработки большого объема запросов, но чаще такие проблемы бывают от криво написаного кода.
Mad_Dollar
Если кривой именно код - много лишних запросов, неоптимальные сиквел выражения, то оптимизировать базу/схему под него смысла немного, если этот код нужно будет поддерживать. Вобщем заочно сложно советовать.
Mad_Dollar
"Рефакторить схему" - оно конечно надо. Но вот приведенный выше запрос, у меня в первую очередь вызвал такой простой вопрос:

Зачем надо было вычитать в запросе один Count() из другого, для каждой записи? Разве нельзя это сделать уже позже на ПХП... и значительно быстрее?
А ежели учесть, что при таких джойнах записи легко "размножаются" в выдаче, то как таким запросом можно рассчитывать на получение правильных чиселок?

"профи" могут рассказать, где тут "собака порылась"?:улыб:
Mad_Dollar
Ну вот заработало. После добавления указанных выше полей и изменения назначения поля created_by в основной таблице имен списков (clists) на ссылку с пользователя системы на ссылку в сотрудники отдела... вышеприведенный запрос стал выглядеть так:

SELECT
cl.`id`, cl.`name`, cl.`begin_date`, cl.`end_date`, cl.`created_at`,
cl.`companies_count` AS cnt,
COUNT(cfn.`id`) AS not_distributed,
CONCAT_WS(' ', mp.`lname`, mp.`fname`) as creator_name,
NOT ISNULL(cf.`for_deps_at`) as allow_edit,
IF(cf.`m_division_worker_id` = mdw.`id`, true, false) as is_owner
FROM `clists` AS cl
JOIN `clists_m_company` AS cf ON cf.`list_id` = cl.`id`
LEFT JOIN `clists_m_company` AS cfn ON cfn.`id` = cf.`id`
AND (cfn.`m_company_function_id` IS NOT NULL) AND (cfn.`m_division_worker_id` IS NULL)

JOIN `m_division_worker` AS mdw ON mdw.`id` = cl.`created_by`
JOIN `m_company_people` AS mcp ON mcp.`id` = mdw.`m_company_people_id`
JOIN `m_people` AS mp ON mp.`id` = mcp.`m_people_id`

GROUP BY cl.`id`
;

... и выполняться за 0.2 сек.

Или я где-то "не догоняю" или "нафига было так делать"?
tolstopuz
А вот такая редакция дает 54мс:

SELECT
cl.`id`, cl.`name`, cl.`begin_date`, cl.`end_date`, cl.`created_at`,
cl.`companies_count` AS cnt,
SUM(IF(
(NOT ISNULL(cf.`m_company_function_id`)) AND ISNULL(cf.`m_division_worker_id`),
1 , 0)) AS not_distributed
CONCAT_WS(' ', mp.`lname`, mp.`fname`) as creator_name,
NOT ISNULL(cf.`for_deps_at`) as allow_edit
IF(cf.`m_division_worker_id` = cl.`created_by`, true, false) as is_owner
FROM `clists` AS cl
JOIN `clists_m_company` AS cf ON cf.`list_id` = cl.`id`

JOIN `m_division_worker` AS mdw ON mdw.`id` = cl.`created_by`
JOIN `m_company_people` AS mcp ON mcp.`id` = mdw.`m_company_people_id`
JOIN `m_people` AS mp ON mp.`id` = mcp.`m_people_id`

GROUP BY cl.`id`
;

П.С. подправил "кто собственник" для наглядности.
tolstopuz
Я ту конструкцию в виде какого-то пхп подобного ORM запроса вообще не понял, и догадываться было неохота, а тут мне кажется для подсчета cfn джоинить хуже чем выполнить отдельный subselect, хотя может execution plan будет и одинаковый, надо смотреть. И вообще group by id как-то не смотрится (потому и заметил), т.е. вместо

COUNT(cfn.`id`) AS not_distributed,

я бы написал нечто вроде

(select count(1) from clists_m_company cfn where cfn.id = cf.id) AS not_distributed,

а левый джоин убрал бы нафик
tolstopuz
а зачем в новой редакции GROUP BY cl.`id` ?
Камон
Замена джоина на сабселект это к вопросу о читабельности кода.
Если сабселект понятен и однозначен (и query оптимайзеру тоже), то левый джоин с count ом и group by id вызывает легкий несколькосекундный ступор - "нахуа"
Камон
а зачем в новой редакции GROUP BY cl.`id` ?
Иначе SUM не сработает
Камон
"Перевел" на SQL то, что было ранее выдано из ПХП кода. Заодно немножко перегруппировал, дабы было понятнее.
Зачем здесь нужна вторая группа джойнов - так и не понял. Они, вроде как, ваще НЕ используются. А третья группа, относится к получению ФИО автора списка.

SELECT cl.id AS id, cl.name AS name, cl.created_at AS created_at,
COUNT(DISTINCT ccd.id) AS companies_count,
ccf.begin_date AS begin_date,
ccf.end_date AS end_date,
COUNT(DISTINCT ccd.id) - COUNT(DISTINCT ccp.id) AS not_distributed,
CONCAT(IFNULL(p.lname, ''), ' ', IFNULL(p.fname, '')) AS creator_name,
NOT ISNULL(ccf.begin_date) AS allow_edit,
IF(cl.created_by = ccf.owner, true, false) AS isowner,
ccf.id AS ccf_id
FROM `clists` AS cl
LEFT JOIN clists_m_company_function AS ccf ON ccf.list_id = cl.id
LEFT JOIN clists_company_department AS ccd ON ccd.clists_m_company_function_id = ccf.id
LEFT JOIN clists_m_company_people AS ccp ON ccp.clists_m_company_function_id = ccf.id

LEFT JOIN m_division_worker AS dw ON dw.m_company_function_id = ccf.m_company_function_id
LEFT JOIN m_company_people AS cp ON dw.m_company_people_id = cp.id
LEFT JOIN usr_users AS u ON u.`data_type` = 'm_company_people' AND u.data_id = cp.id

LEFT JOIN usr_users AS u2 ON u2.id = cl.created_by
LEFT JOIN m_company_people AS cp2 ON u2.data_id = cp2.id AND u2.data_type = 'm_company_people'
LEFT JOIN m_people AS p ON p.id = cp2.m_people_id
;
tolstopuz
Щас вот вывалился Auth.php... апосля того, как добавил в его инициализацию дополнительную выборку из базы параметров "сотрудник", "сотрудник отдела", "ФИО"... это дабы такие джойны поубивать ваще из всех запросов нафиг... (по три таблицы, как в примерах выше)

... оказалось, что плагин запускается раньше, чем происходит инициализация всех настроек в ZendRegistry ... при попытке выборки из БД дополнительных данных - выдал ошибку "не установлен параметр в ZendRegisry"...

Вот и ещё вопросик: в каком порядке лучше проводить инициализацию в index.php?:улыб:
tolstopuz
Вы бы уж весь исходник выложили - да и набирали группу энтузиастов для поддержки:улыб:
А то как-то не понятно уже к чему весь этот поток мыслей.
KSergey
Весь сюда, пожалуй не влезет. Он, думаю побольше чем весь этот сайт, причем раз в несколько.

"Весь этот поток мыслей" - надеялся на помощь местного сообщества. В частности в виде ответов на вот эти вопросы:

1. Как лучше: 1) if ( !is_null($data) ) {...} или 2) if ( null === $data ) {...} ... или в ПХП is_null() - не вызов функции?
Практически разобрался сам...

2. Зачем надо было вычитать в запросе один Count() из другого, для каждой записи? Разве нельзя это сделать уже позже на ПХП... и значительно быстрее?
... ну, фактически вопрос закрыт. По факту: 33.5минуты против 54мсек., "мелочь, а приятно".:миг:

3. А ежели учесть, что при таких джойнах записи легко "размножаются" в выдаче, то как таким запросом можно рассчитывать на получение правильных чиселок?
... тоже.

4. Вот и ещё вопросик: в каком порядке лучше проводить инициализацию в index.php?
... пока осталось. Сижу, читаю Зенд, сравниваю в коде... пытаюсь "переварить".

Вот, где-то так. Ещё раз: ПХП, Мускуль, Зенд - я ещё только осваиваю...
Как оказалось, на "помощь" рассчитывать - не приходится. "Знатоки" - примерно такие же как и те, кто это писал - как и сам на сегодня.

Если интересно, могу дальше выкладывать старые и измененные части на "ваш суд".:улыб:

А так, в принципе, тему можно закрывать. Исходники будут в доступе (из SVN) с 15-го февраля, для всех фрилансеров, желающих подработать.
tolstopuz
ищите проф. форумы, к примеру

http://www.sql.ru/forum/actualforum.aspx

там и почитать и спросить есть кого, в смысле народа там реально больше и шансы на ответ выше. Там даже про пхп есть.
Камон
пасибки. Посмотрел, есть чего почитать... очень нехватало.

Понравился FAQ по мускулю... особенно статья о деревьях.
tolstopuz
Возник ещё вопросик:

Добавляю в класс, наследник Zend_Db_Table (работа с таблицей мускуля) константы, содержащие наименования полей...
Оказалось достаточно удобно, а главное "не ошибешься" в написании...
Но тут вот задумался: если я, используя класс в другой модели, обращаюсь только к константам класса - будет подгружаться весь файл класса, так ведь? А если в БД 300 таблиц... то "в пределе" можно загрузить все классы, так чтоли?

А как вообще правильно реализовывать на Зенде работу с зависимыми таблицами, когда надо, чтобы выборка производилась как со стороны родительской, так и зависимой таблицы...
tolstopuz
Спасибки всем, с последним вопросом разобрался так:
Классы-наследники Zend_Db_Table дополнил:
1. Наименования полей из таблицы БД - как константы класса.
2. Метод класса, возвращающий строку join, leftjoin.
Кроме этого добавил несколько простых обычных функций:
1. по формирование строки "where" из mixed параметра (null, int, string, array), которая правильно использует "=", "is null", "in()"
2. преобразование массива Rowset в простой массив "ключ"=>"значение".

Оказалось очень удобно. Теперь если надо изменить структуру таблицы класса - достаточно только добавить/изменить константы класса. Код лопатить - нет никакой нужды. А главное больше не надо каждый раз пялиться в БД и смотреть какие там поля в таблице ишо есть...