Анализ причины аномального выполнения SQL в производственной системе

Java база данных MySQL сервер
Анализ причины аномального выполнения SQL в производственной системе

причина

В последнее время производственная система продолжала сталкиваться с некоторыми исключениями базы данных, что приводило к сбою выполнения SQL.

Среда приложения

Java 1.7 + Mysql 5.6 + spring + ibatis

Устранение неполадок

Провал различных аномалий фиксируют немного, вплоть до следующих типов встречающихся аномалий.

  1. java.net.SocketTimeoutException: Read timed out
  2. java.sql.BatchUpdateException: после закрытия оператора операции не допускаются.
  3. com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
    • java.io.EOFException: Can not read response from server. Expected to read 8 bytes, read 7 bytes before connection was unexpectedly lost.
    • java.net.SocketException: Software caused connection abort: recv failed

SocketTimeoutException

Для первого приведенного выше случая легко нарисовать буквальный тайм-аут чтения. Однако существуют различные таймауты для запроса данных JDBC, внимательно изучите его и разберитесь.

JBDC может установить время ожидания как время ожидания транзакции, время ожидания оператора, время ожидания сокета и время ожидания соединения. Вышеупомянутые уровни тайм-аута расположены сверху вниз.

Ниже мы разберемся в этих видах таймаутов сверху и снизу.

Время ожидания транзакции: время ожидания транзакции, состоящее из нескольких операторов. Время ожидания транзакции = N*Statement.timeout + другое время выполнения кода. Поэтому нам не следует выполнять какие-то длительные вызовы, такие как RPC или HTTP, за одну транзакцию. Если время на этих вызовах застрянет, это приведет к истечению времени ожидания транзакции и откату.

Время ожидания оператора: время выполнения оператора, которое можно использовать для ограничения времени выполнения оператора запроса. Но в случае сбоя сети этот тайм-аут не сработает. В конце концов Socket TimeOut необходимо разрешить.

Socket TimeOut: в настоящее время существует четыре типа JDBC, и мы обычно используем драйвер протокола базы данных (драйвер базы данных-протокола (драйвер Pure Java) или тонкий драйвер). Этот драйвер использует Socket для связи с базой данных. Если не задано, при сбое сети чтение SCOKET будет напрямую заблокировано. После установки java.net.SocketTimeoutException: превышение времени ожидания чтения будет сгенерировано по истечении времени, что предотвратит долгосрочную блокировку и недоступность системы.

ConnectionTimeout: этот параметр тайм-аута также связан с установлением соединения сокетом. Если он не установлен, как только параметры адреса, связанные с базой данных, будут неправильными, он будет блокировать установление соединения с базой данных в течение длительного времени.

С помощью картинки в Интернете можно наглядно проанализировать отношения между первыми тремя.

超时关系图

На самом деле тайм-аут сокета есть и на уровне операционной системы. Каждая операционная система может установить соответствующий период ожидания сокета, и если JDBC не установлен, период ожидания операционной системы также будет отключен. Но мы не можем полагаться на этот тайм-аут, потому что это время совершенно неуправляемо, и мы должны установить его явно.

Подводя итог, нам нужно установить по крайней мере ConnectionTimeout и Socket TimeOut для связанных параметров JDBC.Для операторов sql может быть установлено время ожидания оператора. Если есть транзакция, вы также можете установить соответствующий тайм-аут транзакции.

com.mysql.jdbc.exceptions.jdbc4.CommunicationsException

Это исключение CommunicationsException вызовет следующие два исключения из-за других базовых исключений.

  1. java.io.EOFException: Can not read response from server. Expected to read 8 bytes, read 7 bytes before connection was unexpectedly lost.
  2. java.net.SocketException: Software caused connection abort: recv failed

Когда я впервые столкнулся с этим исключением, я проверил его в соответствии с CommunicationsException, что обычно означает, что серверная часть Mysql будет обнаруживать незанятые соединения и активно отключаться по истечении времени ожидания, что приводит к сбою соединения клиента.

那么什么是 mysql 的空闲连接那?简单来说,mysql 连接进程 Command 为 sleep 状态。 мы можем использоватьshow processlist ;Посмотреть запущенный процесс. Примеры бездействующих процессов:

空闲进程状态

jdbc-соединения обнаруживают незанятые соединения на основе mysql wait_timeout. Если соединение все еще простаивает в течение времени ожидания, сервер mysql отключит соединение. В этом случае используется имитация кодирования. Используйте следующий код:

		try {
			Connection connection = dataSource.getConnection();
			TimeUnit.SECONDS.sleep(11L);
			run.query(connection,"select 'X'", h);
			//Thread.sleep(60000);
		} catch (Exception e) {
			log.error("查询异常", e);
		}

Затем установите mysql wait_timeout=10 . После того, как следующий код моделирования получает соединение, он спит в течение 11 с.Во время этого процесса mysql активно отключает соединение.Когда он фактически выполняется, программа выдает исключение.

Сообщается следующее об ошибке:

报错情况

Но основным исключением является java.net.SocketException: программное обеспечение вызвало прерывание соединения: сбой recv, а не java.io.EOFException.

Эта ошибка очень подозрительна. Затем внимательно посмотрите на описание EOFException.Expected to read 8 bytes, read 7 bytes before connection was unexpectedly lost, видно, что это соединение на самом деле все еще доступно в течение определенного периода времени, и есть данные для чтения, но в процессе чтения данных соответствующие данные, соответствующие количеству, не считываются, что приводит к ошибке. Вышеприведенный код имитирует ситуацию, когда соединение вступило в силу, когда соединение используется.

воплощать в жизньshow variables like '%timeout%';Посмотреть другие тайм-ауты mysql,

mysql 其他超时时间

Из приведенного выше рисунка видно, чтоnet_read_timeoutиnet_write_timeoutэти два параметра.

Посмотреть официальную документацию mysql

net_read_timeoutПо умолчанию 30 с Количество секунд ожидания дополнительных данных от соединения перед прекращением чтения. Когда сервер читает от клиента, net_read_timeout — это значение тайм-аута, определяющее время прерывания. Когда сервер пишет клиенту,net_write_timeoutПо умолчанию 60s Количество секунд, чтобы дождаться блока, которое будет записано в соединение, прежде чем прервать запись

net_write_timeout контролирует время ожидания для сервера mysql для записи данных клиенту. В этом случае поставьте короткую точку на чтение MysqlIO,

断点情况

Когда программа запустится, сначала отпустите точку останова, проверьте список процессов mysql и посмотрите, как состояние процесса mysql отправляется клиенту, и точка останова вступит в силу в это время. В это время после ожидания 60 с успешно появляется следующая ошибка.

异常

net_read_timeout этот тайм-аут не умеет имитировать :(.

Подводя итог, можно сказать, что если возникает исключение com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: сбой канала связи, соединение с базой данных завершается сбоем, но могут быть различные причины сбоя, которые обычно связаны с различными параметрами тайм-аута mysql.

BatchUpdateException

Эта ошибка возникает, когда данные импортируются пакетами. На тот момент объем данных был примерно больше 20W, а потом при пакетной вставке выбрасывалось исключение. Ниже приведен код вставки пакета.

getSqlMapClientTemplate().execute(new SqlMapClientCallback<Object>() {
			@Override
			public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
				executor.startBatch();
				for (int i = 0; i < 200000; i++) {
					Demo demo = new Demo();
					demo.setName("asd");
					demo.setAge(String.valueOf(i));
					demo.setSubject("adassad");
					// 原项目 这里会发生一次 RPC调用 现用 Sleep 代替
					try {
						TimeUnit.MILLISECONDS.sleep(10L);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					executor.insert("insertDemo", demo);
				}
				executor.executeBatch();
				return null;
			}
		});

Этот код использует функцию Batch ibatis для вставки данных в партии.

На самом деле, мы видим эту информацию об исключении, java.sql.BatchUpdateException: ясно видно, что никакие операции, разрешенные после закрытия оператора, будут закрыты, потому что причина оператора, а затем почему оператор будет закрыт заранее. Здесь мы прослеживаем источник.

Теперь давайте взглянем на метод SqlmapClientCallback DoinsqlmapClient. Отлаживатьexecutor.startBatch()Наконец, метод вызывает метод SqlMapExecutorDelegate.startBatch.

startBatch

Глядя на комментарии к коду, вы можете видеть, что цель состоит в том, чтобы установить значение состояния, которое будет использоваться ниже.

В этот момент мы смотрим наexecutor.insert, Обычно этот метод должен выполнять оператор SQL, а затем вставлять его в базу данных. Но просмотрев исходный код, вы обнаружите, что последним вызовом, который он вызывает, является MappedStatement.sqlExecuteUpdate, а установленный выше атрибут пакета сеанса оценивается в начале метода входа. Конечно, вначале это свойство было установлено в true, поэтому в настоящее время действие вставки sql не выполняется, но этот sql и связанные с ним параметры хранятся в памяти.

  protected int sqlExecuteUpdate(StatementScope statementScope, Connection conn, String sqlString, Object[] parameters) throws SQLException {
    if (statementScope.getSession().isInBatch()) {
      getSqlExecutor().addBatch(statementScope, conn, sqlString, parameters);
      return 0;
    } else {
      return getSqlExecutor().executeUpdate(statementScope, conn, sqlString, parameters);
    }
  }

Наконец мы смотримexecutor.executeBatchНаконец, метод называетсяStatement.executeBatch, и фактически начать выполнять массовые вставки.

После прочтения логики в SqlMapClientCallback теперь давайте посмотрим на логику кода SqlMapClientTemplate.execute.

https://www.ibm.com/developerworks/cn/java/j-lo-ibatis-principle/index.html

Глядя на диаграмму последовательности, мы видим, что когда логика метода обратного вызова SqlMapClientCallback фактически выполняется, сначала будет получено соединение из источника данных, а затем будет выполнена логика обратного вызова SqlMapClientCallback, и, наконец, соединение будет освобождено. В этом процессе, если время выполнения метода SqlMapClientCallback слишком велико, например, вызов Dubbo будет происходить каждый раз, когда цикл for вызывается в нашем методе, а затем, поскольку этому циклу необходимо пройти более 20 Вт данных, он для завершения цикла потребуется более половины времени (при условии, что вызов dubbo занимает 10 мс), а время ожидания нашего сервера mysql составляет 300 с, поэтому сервер mysql заранее активно освобождает незанятые соединения, а затем ждет, пока пакетная вставка не будет фактически завершена. выполняется, что вызовет указанное выше исключение.

Не по теме: когда mysql jdbc использует пакетную вставку, ее необходимо установитьrewriteBatchedStatements=trueпараметр. Если он не установлен, это эквивалентно вставке данных с использованием цикла for в конце, что не повышает эффективность вставки.

Ссылаться на

  1. Тип драйвера JDBC
  2. Understanding JDBC Internals & Timeout Configuration
  3. Глубокое понимание тайм-аутов JDBC
  4. Углубленный анализ механизма тайм-аута JDBC
  5. mysql: показать подробное объяснение списка процессов
  6. Расскажите о настройке jdbc socketTimeout

Если вы считаете, что это хорошо, пожалуйста, поставьте автору большой палец вверх~ Спасибо.

Читатели, которым понравилась эта статья, добро пожаловать в долгое нажатие, чтобы следить за сообщением программы номера подписки ~ позвольте мне поделиться программой с вами.