ee569250ad
Database Portability Tests / Unit Tests (PlatformHelper) (push) Failing after 40s
Database Portability Tests / Integration (mysql) (push) Has been skipped
Database Portability Tests / Integration (postgres) (push) Has been skipped
Database Portability Tests / Integration (sqlite) (push) Has been skipped
Database Portability Tests / Verify no MySQL-specific SQL (push) Successful in 5s
- MemberMapper: 8 new *WithRelations() methods that fetch members with addresses, phones, and emails in a single query using LEFT JOINs - MemberMapper: addJoinClauses() and fetchWithRelations() private helpers that handle JOIN duplication (one member × multiple sub-entities) - MemberService: refactored findAll, findByFamily, findByStatus, search, findByBirthdayThisMonth, findWithUnpaidFees, findFiltered, fullTextSearch to delegate to joined mapper methods - MemberService: added arrayToMember() and arrayToAddress() helpers so buildMatchContext() works with flat-array results from fullTextSearch - MemberServiceTest: updated all existing tests to mock new method names and return flat-array format with nested sub-entities - MemberServiceTest: added 10 new tests covering joined methods, backward compatibility, and correct shape of returned data - Moved issue-200 plan from open/ to done/
1.8 KiB
1.8 KiB
Issue #200: N+1 Query Problem in Member List
Problem
The MemberService::findAll() method (and similar methods) loads members, then makes 3 additional queries per member to fetch addresses, phones, and emails:
public function findAll(?int $limit = null, ?int $offset = null): array {
$members = $this->memberMapper->findAll($limit, $offset);
return array_map(function (Member $member) {
$id = $member->getId();
$addresses = $this->addressMapper->findByMemberId($id); // +1 query
$phones = $this->phoneMapper->findByMemberId($id); // +1 query
$emails = $this->emailMapper->findByMemberId($id); // +1 query
return $this->buildMemberResponse($member, $addresses, $phones, $emails);
}, $members);
}
For a page of 20 members, this results in 1 + 20×3 = 61 queries.
Impact
- Slow page loads, especially with pagination
- Increased database load
- Poor performance on larger member lists
Solution
Create a new mapper method that uses JOINs to fetch all data in a single query:
SELECT m.*, a.*, p.*, e.*
FROM mv_members m
LEFT JOIN mv_addresses a ON m.id = a.member_id
LEFT JOIN mv_phones p ON m.id = p.member_id
LEFT JOIN mv_emails e ON m.id = e.member_id
WHERE m.deleted_at IS NULL
ORDER BY m.nachname, m.vorname
Then parse the result set in PHP to group related sub-entities.
Tasks
- Create
MemberMapper::findAllWithRelations(?int $limit, ?int $offset): arraythat returns raw joined data - Create a DTO or use an associative array to represent joined results
- Update
MemberService::findAll()to use the new method - Apply same pattern to
findFiltered(),findByFamily(),search(),fullTextSearch() - Benchmark before/after query counts
Labels
- enhancement
- backend
- priority:high
- performance