Inefficient Query Confusion

…or try this:

// ... put your $today and $future variables here...

global $wpdb;

$events = $wpdb->get_results(
" SELECT *

  FROM {$wpdb->posts}

  LEFT JOIN(
   SELECT DISTINCT post_id,
    (SELECT CAST(meta_value AS DATE) FROM {$wpdb->postmeta} WHERE {$wpdb->postmeta}.post_id = meta.post_id AND meta_key ='opening_time') AS opening_time,
    (SELECT CAST(meta_value AS DATE) FROM {$wpdb->postmeta} WHERE {$wpdb->postmeta}.post_id = meta.post_id AND meta_key ='artist_talk_time') AS artist_talk_time,
    (SELECT CAST(meta_value AS DATE) FROM {$wpdb->postmeta} WHERE {$wpdb->postmeta}.post_id = meta.post_id AND meta_key ='closing_time') AS closing_time,
    (SELECT CAST(meta_value AS DATE) FROM {$wpdb->postmeta} WHERE {$wpdb->postmeta}.post_id = meta.post_id AND meta_key ='special_event_time') AS special_event_time,
    FROM {$wpdb->postmeta} meta )
   AS mymeta ON {$wpdb->posts}.ID = mymeta.post_id

  WHERE
        post_status="publish" 
    AND post_type="event" 
    AND ( 
          mymeta.opening_time BETWEEN {$today} AND {$future}
       OR mymeta.artist_talk_time BETWEEN {$today} AND {$future}
       OR mymeta.closing_time BETWEEN {$today} AND {$future}
       OR mymeta.special_event_time BETWEEN {$today} AND {$future}
      )

  GROUP BY ID
  ORDER BY opening_time
  LIMIT 0,10
");

 var_dump($events); // you should get a list of 10 events here, ordered like you wanted

Theoretically it should be faster than what meta_query generates…