How can I loop only months foreach year?

I have tried your query. It plays in right way. I think the problem is your handling of current year in the foreach loop.

I suppose you would like to achieve this effect, right?

// it is the test result based on some dummy posts
2020
 March
2000
 October
1999
 March
 January
1986
 September
1981
 November

Here is a simplified version to illustrate the iteration.

<?php

// your original query, the result object is in right structure for iteration
global $wpdb;
$year_prev = null;
$current_post_type = get_post_type();
$months = $wpdb->get_results("SELECT DISTINCT MONTH( post_date ) AS month , YEAR( post_date ) AS year, COUNT( id ) as post_count FROM $wpdb->posts WHERE post_status="publish" and post_date <= now( ) and post_type="$current_post_type" GROUP BY month , year ORDER BY post_date DESC");

// iterate the result
$current_year=""; // for first time
foreach($months as $month) : 
    // if it is first time or it is not the same as before
    // echo a close list (for 2nd time or after, must do it before assign a new year)
    // assign a new year
    if( empty( $current_year ) || $current_year != $month->year ) {
        if( ! empty( $current_year ) ) {
            echo '</ul>';
        }

        $current_year = $month->year;
        echo '<h3>' . $month->year . '年</h3>';
        echo '<ul class="side-list">';
    }

    // use the internal locale function to get a named month from the number
    echo  '<li>' . $wp_locale->get_month( $month->month ). '</li>';
endforeach;
?>