WP doesn’t “look” at the type of data you have, it just has a default of CHAR. So if you dump your current query you will see respective type cast: CAST(wp_postmeta.meta_value AS CHAR)
.
To make it work you need to specify type argument explicitly 'type' => 'NUMERIC'
and CAST will change accordingly: CAST(wp_postmeta.meta_value AS SIGNED)
.
See Custom Field Parameters in Codex for more on supported types.
For the record here is how you can dump generated SQL, without involving too many moving parts:
$meta_query_args = array(
'relation' => 'OR',
array(
'key' => 'price',
'value' => array( 80, 100 ),
'compare' => 'BETWEEN',
'type' => 'NUMERIC',
),
);
$meta_query = new WP_Meta_Query( $meta_query_args );
$sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
/*
array (2) [
'join' => string (64) " INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )"
'where' => string (112) " AND (
( wp_postmeta.meta_key = 'price' AND CAST(wp_postmeta.meta_value AS SIGNED) BETWEEN '80' AND '100' )
)"
]
*/