A meta_query is a join, and it shows

Every meta key in a meta_query adds a join to wp_postmeta, and that table has one row per key per post with an index that was designed for lookup by post rather than by value.

// three joins, and a filesort
$q = new WP_Query( array(
    'post_type'  => 'product',
    'meta_query' => array(
        array( 'key' => 'in_stock',  'value' => '1' ),
        array( 'key' => 'sector',    'value' => 'retail' ),
        array( 'key' => 'price', 'value' => 5000, 'compare' => '<', 'type' => 'NUMERIC' ),
    ),
) );

// what to do instead when this is a listing page:
// a taxonomy for the categorical fields, or a custom table

The meta_value column is a longtext with a partial index, so a numeric comparison casts every row and cannot use it at all. Categorical values almost always want to be a taxonomy, which is indexed for exactly this. For anything genuinely relational the honest answer is a custom table with the columns and indexes the query needs, and accepting that it is outside the WordPress data model.