]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5486 DB migration hangs on the MeasuresDebtToMinutes step with MsSQL (dead...
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 28 Jul 2014 09:05:06 +0000 (11:05 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 28 Jul 2014 16:46:40 +0000 (18:46 +0200)
sonar-server/src/main/java/org/sonar/server/db/migrations/MassUpdater.java
sonar-server/src/main/java/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigration.java
sonar-server/src/test/java/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigrationTest.java
sonar-server/src/test/resources/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigrationTest/migrate_nothing.xml [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigrationTest/migrate_nothing_result.xml [new file with mode: 0644]

index 621d92fc15b47b680b48e8bf717e79645dcdcd14..306b8f1234868cf175ab7e5e574f8ba20b07cabb 100644 (file)
@@ -30,7 +30,12 @@ import org.sonar.core.persistence.dialect.MySql;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
-import java.sql.*;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Update a table by iterating a sub-set of rows. For each row a SQL UPDATE request
@@ -39,7 +44,7 @@ import java.sql.*;
 public class MassUpdater {
 
   private static final Logger LOGGER = LoggerFactory.getLogger(MassUpdater.class);
-  private static final int DEFAULT_GROUP_SIZE = 1000;
+  private static final int DEFAULT_GROUP_SIZE = 250;
   private final Database db;
   private final int groupSize;
 
@@ -76,6 +81,30 @@ public class MassUpdater {
     boolean update(Connection writeConnection) throws SQLException;
   }
 
+  public List<Long> selectLong(String sql) {
+    Connection readConnection = null;
+    PreparedStatement stmt = null;
+    ResultSet rs = null;
+    try {
+      readConnection = db.getDataSource().getConnection();
+      readConnection.setAutoCommit(false);
+
+      stmt = initStatement(sql, readConnection);
+      rs = stmt.executeQuery();
+      List<Long> rows = new ArrayList<Long>();
+      while (rs.next()) {
+        if (!rs.wasNull()) {
+          rows.add(rs.getLong(1));
+        }
+      }
+      return rows;
+    } catch (Exception e) {
+      throw processError(e);
+    } finally {
+      DbUtils.closeQuietly(readConnection, stmt, rs);
+    }
+  }
+
   public <S> void execute(InputLoader<S> inputLoader, InputConverter<S> converter) {
     execute(inputLoader, converter, null);
   }
@@ -83,15 +112,18 @@ public class MassUpdater {
   public <S> void execute(InputLoader<S> inputLoader, InputConverter<S> converter, @Nullable PeriodicUpdater periodicUpdater) {
     long count = 0;
     Connection readConnection = null, writeConnection = null;
-    Statement stmt = null;
+    PreparedStatement stmt = null;
     ResultSet rs = null;
     PreparedStatement writeStatement = null;
     try {
       readConnection = db.getDataSource().getConnection();
       readConnection.setAutoCommit(false);
+      if (readConnection.getMetaData().supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED)) {
+        readConnection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
+      }
 
-      stmt = initStatement(readConnection);
-      rs = stmt.executeQuery(convertSelectSql(inputLoader.selectSql(), db));
+      stmt = initStatement(convertSelectSql(inputLoader.selectSql(), db), readConnection);
+      rs = stmt.executeQuery();
 
       int cursor = 0;
       while (rs.next()) {
@@ -100,7 +132,7 @@ public class MassUpdater {
           continue;
         }
 
-        if (writeConnection==null) {
+        if (writeConnection == null) {
           // do not open the write connection too early
           // else if the select  on read connection is long, then mysql
           // write connection fails with communication failure error because
@@ -118,12 +150,12 @@ public class MassUpdater {
         }
 
         if (cursor == groupSize) {
-          commit(writeConnection, writeStatement, periodicUpdater);
+          commit(writeStatement, periodicUpdater);
           cursor = 0;
         }
       }
       if (cursor > 0) {
-        commit(writeConnection, writeStatement, periodicUpdater);
+        commit(writeStatement, periodicUpdater);
       }
 
     } catch (SQLException e) {
@@ -139,22 +171,22 @@ public class MassUpdater {
     }
   }
 
-  private Statement initStatement(Connection readConnection) throws SQLException {
-    Statement stmt = readConnection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+  private PreparedStatement initStatement(String sql, Connection readConnection) throws SQLException {
+    PreparedStatement stmt = readConnection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
     if (db.getDialect().getId().equals(MySql.ID)) {
       stmt.setFetchSize(Integer.MIN_VALUE);
     } else {
-      stmt.setFetchSize(groupSize);
+      stmt.setFetchSize(1000);
     }
     return stmt;
   }
 
-  private void commit(Connection writeConnection, PreparedStatement writeStatement, @Nullable PeriodicUpdater periodicUpdater) throws SQLException {
+  private void commit(PreparedStatement writeStatement, @Nullable PeriodicUpdater periodicUpdater) throws SQLException {
     writeStatement.executeBatch();
     if (periodicUpdater != null) {
-      periodicUpdater.update(writeConnection);
+      periodicUpdater.update(writeStatement.getConnection());
     }
-    writeConnection.commit();
+    writeStatement.getConnection().commit();
   }
 
   private static RuntimeException processError(Exception e) {
index 9dd4ab82a4b84bd737ceec3db8aa4d30aa9a71ff..e63bfdd30de2f543ba1f52d868ea3e458751bad4 100644 (file)
@@ -30,24 +30,15 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
+import java.util.List;
 
 /**
  * Used in the Active Record Migration 515
+ *
  * @since 4.3
  */
 public class TechnicalDebtMeasuresMigration implements DatabaseMigration {
 
-  private static final String SELECT_SQL = "SELECT pm.id, pm.value " +
-    ", pm.variation_value_1 , pm.variation_value_2, pm.variation_value_3 " +
-    ", pm.variation_value_4 , pm.variation_value_5 " +
-    " FROM project_measures pm INNER JOIN metrics m on m.id=pm.metric_id " +
-    " WHERE (m.name='sqale_index' or m.name='new_technical_debt' " +
-    // SQALE measures
-    " or m.name='sqale_effort_to_grade_a' or m.name='sqale_effort_to_grade_b' or m.name='sqale_effort_to_grade_c' or m.name='sqale_effort_to_grade_d' " +
-    " or m.name='blocker_remediation_cost' or m.name='critical_remediation_cost' or m.name='major_remediation_cost' or m.name='minor_remediation_cost' " +
-    " or m.name='info_remediation_cost' " +
-    ")";
-
   private static final String UPDATE_SQL = "UPDATE project_measures SET value=?," +
     "variation_value_1=?,variation_value_2=?,variation_value_3=?,variation_value_4=?,variation_value_5=? WHERE id=?";
 
@@ -63,45 +54,67 @@ public class TechnicalDebtMeasuresMigration implements DatabaseMigration {
   public void execute() {
     workDurationConvertor.init();
 
-    new MassUpdater(db).execute(
-      new MassUpdater.InputLoader<Row>() {
-        @Override
-        public String selectSql() {
-          return SELECT_SQL;
-        }
-
-        @Override
-        public Row load(ResultSet rs) throws SQLException {
-          Row row = new Row();
-          row.id = SqlUtil.getLong(rs, 1);
-          row.value = SqlUtil.getDouble(rs, 2);
-          row.var1 = SqlUtil.getDouble(rs, 3);
-          row.var2 = SqlUtil.getDouble(rs, 4);
-          row.var3 = SqlUtil.getDouble(rs, 5);
-          row.var4 = SqlUtil.getDouble(rs, 6);
-          row.var5 = SqlUtil.getDouble(rs, 7);
-          return row;
+    MassUpdater massUpdater = new MassUpdater(db);
+
+    final List<Long> metricIds = massUpdater.selectLong("select id from metrics " +
+      "where name='sqale_index' or name='new_technical_debt' " +
+      "or name='sqale_effort_to_grade_a' or name='sqale_effort_to_grade_b' or name='sqale_effort_to_grade_c' " +
+      "or name='sqale_effort_to_grade_d' or name='blocker_remediation_cost' or name='critical_remediation_cost' " +
+      "or name='major_remediation_cost' or name='minor_remediation_cost' or name='info_remediation_cost'");
+
+    if (!metricIds.isEmpty()) {
+      massUpdater.execute(
+        new MassUpdater.InputLoader<Row>() {
+          @Override
+          public String selectSql() {
+            StringBuilder sql = new StringBuilder("SELECT pm.id, pm.value " +
+              ", pm.variation_value_1 , pm.variation_value_2, pm.variation_value_3 " +
+              ", pm.variation_value_4 , pm.variation_value_5 " +
+              " FROM project_measures pm " +
+              " WHERE pm.metric_id IN (");
+            for (int i = 0; i < metricIds.size(); i++) {
+              sql.append(Long.toString(metricIds.get(i)));
+              if (i < metricIds.size() - 1) {
+                sql.append(",");
+              }
+            }
+            sql.append(")");
+            return sql.toString();
+          }
+
+          @Override
+          public Row load(ResultSet rs) throws SQLException {
+            Row row = new Row();
+            row.id = SqlUtil.getLong(rs, 1);
+            row.value = SqlUtil.getDouble(rs, 2);
+            row.var1 = SqlUtil.getDouble(rs, 3);
+            row.var2 = SqlUtil.getDouble(rs, 4);
+            row.var3 = SqlUtil.getDouble(rs, 5);
+            row.var4 = SqlUtil.getDouble(rs, 6);
+            row.var5 = SqlUtil.getDouble(rs, 7);
+            return row;
+          }
+        },
+        new MassUpdater.InputConverter<Row>() {
+          @Override
+          public String updateSql() {
+            return UPDATE_SQL;
+          }
+
+          @Override
+          public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
+            setDouble(updateStatement, 1, row.value);
+            setDouble(updateStatement, 2, row.var1);
+            setDouble(updateStatement, 3, row.var2);
+            setDouble(updateStatement, 4, row.var3);
+            setDouble(updateStatement, 5, row.var4);
+            setDouble(updateStatement, 6, row.var5);
+            updateStatement.setLong(7, row.id);
+            return true;
+          }
         }
-      },
-      new MassUpdater.InputConverter<Row>() {
-        @Override
-        public String updateSql() {
-          return UPDATE_SQL;
-        }
-
-        @Override
-        public boolean convert(Row row, PreparedStatement updateStatement) throws SQLException {
-          setDouble(updateStatement, 1, row.value);
-          setDouble(updateStatement, 2, row.var1);
-          setDouble(updateStatement, 3, row.var2);
-          setDouble(updateStatement, 4, row.var3);
-          setDouble(updateStatement, 5, row.var4);
-          setDouble(updateStatement, 6, row.var5);
-          updateStatement.setLong(7, row.id);
-          return true;
-        }
-      }
-    );
+      );
+    }
   }
 
   private void setDouble(PreparedStatement statement, int index, Double value) throws SQLException {
index 5372118b0df45f7368c08f4767bf50618c7b63af..34e2574427ba289dab4fda57597e49e1ad072bfc 100644 (file)
@@ -50,6 +50,15 @@ public class TechnicalDebtMeasuresMigrationTest {
     migration = new TechnicalDebtMeasuresMigration(db.database(), propertiesDao);
   }
 
+  @Test
+  public void migrate_nothing() throws Exception {
+    db.prepareDbUnit(getClass(), "migrate_nothing.xml");
+
+    migration.execute();
+
+    db.assertDbUnit(getClass(), "migrate_nothing.xml", "project_measures");
+  }
+
   @Test
   public void migrate_technical_debt_measures() throws Exception {
     db.prepareDbUnit(getClass(), "migrate_technical_debt_measures.xml");
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigrationTest/migrate_nothing.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigrationTest/migrate_nothing.xml
new file mode 100644 (file)
index 0000000..d4e3f4a
--- /dev/null
@@ -0,0 +1,11 @@
+<dataset>
+
+  <metrics delete_historical_data="[null]" id="2" name="nclc" VAL_TYPE="INTEGER" DESCRIPTION="[null]"  domain="[null]" short_name=""
+           enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+  <project_measures id="2" VALUE="1.0" METRIC_ID="2" SNAPSHOT_ID="1000" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+                    RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+                    alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]" person_id="[null]"
+                    variation_value_1="1.0" variation_value_2="2.0" variation_value_3="3.0" variation_value_4="4.0" variation_value_5="5.0"/>
+
+</dataset>
diff --git a/sonar-server/src/test/resources/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigrationTest/migrate_nothing_result.xml b/sonar-server/src/test/resources/org/sonar/server/db/migrations/v43/TechnicalDebtMeasuresMigrationTest/migrate_nothing_result.xml
new file mode 100644 (file)
index 0000000..d4e3f4a
--- /dev/null
@@ -0,0 +1,11 @@
+<dataset>
+
+  <metrics delete_historical_data="[null]" id="2" name="nclc" VAL_TYPE="INTEGER" DESCRIPTION="[null]"  domain="[null]" short_name=""
+           enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+  <project_measures id="2" VALUE="1.0" METRIC_ID="2" SNAPSHOT_ID="1000" alert_text="[null]" RULES_CATEGORY_ID="[null]"
+                    RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
+                    alert_status="[null]" description="[null]" rule_priority="[null]" characteristic_id="[null]" url="[null]" person_id="[null]"
+                    variation_value_1="1.0" variation_value_2="2.0" variation_value_3="3.0" variation_value_4="4.0" variation_value_5="5.0"/>
+
+</dataset>