Post meta is a key-value store with an index on the key, and querying it by value is a join per condition with no useful index.
-- three meta conditions = three joins
SELECT p.* FROM wp_posts p
JOIN wp_postmeta m1 ON m1.post_id = p.ID AND m1.meta_key = 'price'
JOIN wp_postmeta m2 ON m2.post_id = p.ID AND m2.meta_key = 'stock'
JOIN wp_postmeta m3 ON m3.post_id = p.ID AND m3.meta_key = 'brand'
WHERE m1.meta_value < '5000' AND m2.meta_value > '0';
-- and meta_value is LONGTEXT: that comparison is a string one.
The string comparison is the bug people meet first — '9' > '50' is true — and casting in the query removes any chance of using an index. A custom table with typed columns and real indexes is the right answer once a post type has more than a handful of queryable attributes, and the cost is that everything WordPress does for free must now be written: the admin columns, the REST exposure, the deletion cascade.