OceanBase especial INT y problema de conversión implícita de tipo de tiempo

El autor de este artículo compartió las ideas para solucionar el problema de que el resultado de la consulta no cumple con las expectativas o es "incorrecto" debido a la conversión implícita de los tipos de tiempo y valor de Oceanbase.

Autor: Ren Zhongyu

Un miembro del equipo de Acson DBA, bueno en el análisis de fallas y la optimización del rendimiento, bienvenido a discutir problemas técnicos relacionados con el artículo.

Fuente de este artículo: contribución original

  • Producido por la comunidad de código abierto de Aikesheng, no se permite usar el contenido original sin autorización, comuníquese con el editor e indique la fuente para la reimpresión.

Encontré un problema de conversión implícita de tipo de datos "confuso" en el uso de OceanBase anteriormente. La conclusión es relativamente simple, por lo que me gustaría compartir con ustedes las ideas de investigación.

Descripción del problema

Cuando un equipo de proyecto del cliente ejecuta la instrucción SQL de actualización, ocasionalmente fallará y el error es el siguiente:

Después de la desensibilización

ERROR mala gramática de SQL [actualizar renzy establecido en = marca de tiempo actual, expirar_ en = (cast (unix_marca de tiempo (marca de tiempo actual (3) como sin firmar) +?)), order_id =? donde id = ? y (expire_at < current_timestamp or order_id = ?)] java.sql.SQLSyntaxErrorException: (conn=1277168) Valor incorrecto.

Consulta la versión de OceanBase.

./observer -V

observer (OceanBase 3.2.3.2)
REVISION: 105000092022092216-445151f0edb502e00ae5839dfd92627816b2b822

Ver la estructura de la tabla y los datos.

MySQL [test]> show create table renzy\G
*************************** 1. row ***************************
       Table: renzy
Create Table: CREATE TABLE `renzy` (
  `id` varchar(64) NOT NULL,
  `at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `order_id` varchar(64) NOT NULL,
  `expire_at` bigint(20) NOT NULL,
  `vt` timestamp NOT NULL,
  PRIMARY KEY (`id`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 3 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0

MySQL [test]> select * from renzy;
+----+---------------------------+---------------+------------+---------------------------------+
| id | at                  | order_id      | expire_at  | vt                  |
+----+---------------------------+---------------+------------+---------------------------------+
| 1  | 2023-07-07 14:57:13 | 0:[email protected] | 1716040750 | 2023-07-07 14:57:13 |
+----+---------------------------+---------------+------------+---------------------------------+
1 row in set (0.02 sec)

Solución de problemas

Pregunta 1: declaración de error

Ejecute la instrucción SQL que informó el error directamente.

update renzy  set at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:[email protected]'  where id = '1'  and (expire_at < CURRENT_TIMESTAMP  or order_id = '0:[email protected]')

ERROR 1292 (22007): Incorrect values.

Pregunta 2

Sin embargo, cuando la identificación de la clave principal no coincide con ningún dato, UPDATEla declaración no informará un error.

¿por qué? Primero registre el problema y luego mire el registro.

# 表不存在 id=2 的数据
update renzy  set acquired_at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:[email protected]'  where id = '2'  and (expire_at < CURRENT_TIMESTAMP  or order_id = '0:[email protected]')

Query OK.

Salida de sql_auit y registro de error SQL

Analice la salida con el problema sql$gv_audity obtenga la siguiente información:

  • El plan de declaración es un plan local, distribuido al nodo 0.71.
  • El código de error es 4219 ( datetimevalor no válido).
MySQL [oceanbase]> select trace_id,svr_ip,ret_code,retry_cnt,usec_to_time(request_time),elapsed_time,execute_time,plan_type,query_sql from gv$sql_audit where query_sql like 'update id%' and ret_code != 0 order by request_time desc  limit 5;

+-----------------------------------+-------------+----------+-----------+----------------------------+--------------+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| trace_id                          | svr_ip      | ret_code | retry_cnt | usec_to_time(request_time) | elapsed_time | execute_time | plan_type | query_sql                                                                                                                                                                                                                                                                               |
+-----------------------------------+-------------+----------+-----------+----------------------------+--------------+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| YB420CF10047-0005FBCCEF6E3635-0-0 | 12.241.0.71 |    -4219 |         0 | 2023-06-06 15:32:08.375051 |          689 |          611 |         1 | update id  set acquired_at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:[email protected]'  where id = '2'  and (expire_at < CURRENT_TIMESTAMP  or order_id = '0:[email protected]') |
·····
5 rows in set (5.21 sec)

Registro de errores clave

0,72 obs.log.

#grep YB420CF10047-0005FBCCEF6E3635-0-0 observer.log.20230606153309

[2023-06-06 15:32:08.375202] WARN  [LIB.TIME] int_to_ob_time_with_date (ob_time_convert.cpp:1618) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=5] [dc=0] datetime is invalid or out of range(ret=-4219, int64=0)

[2023-06-06 15:32:08.375211] WARN  [LIB.TIME] int_to_datetime (ob_time_convert.cpp:329) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=8] [dc=0] failed to convert integer to datetime(ret=-4219)

[2023-06-06 15:32:08.375214] WARN  [SQL] common_int_datetime (ob_datum_cast.cpp:709) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=3] [dc=0] int_datetime failed(ret=-4219)

[2023-06-06 15:32:08.375219] WARN  [SQL] int_datetime (ob_datum_cast.cpp:2076) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=4] [dc=0] fail to exec common_int_datetime(expr, in_val, ctx, res_datum)(ret=-4219, in_val=1716036728)

[2023-06-06 15:32:08.375223] WARN  [SQL] get_comparator_operands (ob_expr_operator.h:1131) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=4] [dc=0] left eval failed(ret=-4219)

[2023-06-06 15:32:08.375227] WARN  [SQL.ENG] def_relational_eval_func (ob_expr_cmp_func.cpp:54) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=4] [dc=0] failed to eval args(ret=-4219)

[2023-06-06 15:32:08.375232] WARN  [SQL.ENG] calc_or_exprN (ob_expr_or.cpp:221) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=5] [dc=0] eval arg 0 failed(ret=-4219)

[2023-06-06 15:32:08.375240] WARN  [SQL.ENG] filter_row (ob_operator.cpp:915) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=7] [dc=0] expr evaluate failed(ret=-4219, expr=0x7f37808ec058)

[2023-06-06 15:32:08.375247] WARN  [STORAGE] check_filtered (ob_multiple_merge.cpp:1249) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=7] [dc=0] filter row failed(ret=-4219)

[2023-06-06 15:32:08.375251] WARN  [STORAGE] process_fuse_row (ob_multiple_merge.cpp:787) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=3] [dc=0] fail to check row filtered(ret=-4219)

Se observa que el método que arroja el error al principio int_to_ob_time_with_datees "datetime is invalid or out of range(ret=-4219, int64=0)" , es decir el valor no es válido o excede el rango.

El enlace de llamada aquí UPDATEes:

common_int_datetime -> int_to_datetime -> int_to_ob_time_with_date

Pregunta 3: Tratando de usar los resultados de la consulta SELECT

La hora futura (1716040750) se almacena en la tabla EXPIRE_ATComparada con la hora actual (1686042749), el resultado de la consulta no debería mostrar el resultado, ¿verdad? (Registrado por primera vez como Pregunta 3: El resultado de la consulta no cumple con las expectativas)

# 表中只有 1 行记录,且 EXPIRE_AT 的值为 1716040750
MySQL [mock_db]> select * from renzy where EXPIRE_AT < CURRENT_TIMESTAMP;

+-------+---------------------+---------------------+------------+------------------------------------+
| id | ACQUIRED_AT         | order_id            | EXPIRE_AT  | LST_UP_TM          |
+-------+---------------------+---------------------+------------+------------------------------------+
| 1 | 2023-06-06 16:39:10 | 0:[email protected] | 1716040750 | 2023-06-06 16:39:10 |
+-------+---------------------+---------------------+------------+------------------------------------+

1 row in set (0.05 sec)

# 当前时间戳
MySQL [mock_db]> select unix_timestamp(CURRENT_TIMESTAMP);

+--------------------------------------------+
| unix_timestamp(CURRENT_TIMESTAMP) |
+--------------------------------------------+
|                        1686042749 |
+--------------------------------------------+

1 row in set (0.03 sec)

SELECTRegistro de errores de consulta.

[2023-06-06 17:08:54.307371] WARN  [LIB.TIME] int_to_ob_time_with_date (ob_time_convert.cpp:1618) [38763][0][YB420CF10047-0005FBCCEF6F9E6D-0-0] [lt=10] [dc=0] datetime is invalid or out of range(ret=-4219, int64=0)

[2023-06-06 17:08:54.307382] WARN  [LIB.TIME] int_to_datetime (ob_time_convert.cpp:329) [38763][0][YB420CF10047-0005FBCCEF6F9E6D-0-0] [lt=10] [dc=0] failed to convert integer to datetime(ret=-4219)

El enlace de llamada aquí SELECT: int_to_datetime -> int_to_ob_time_with_date

Las anteriores son algunas preguntas que están en duda.Antes del análisis específico, entendamos los puntos de conocimiento previo: la conversión implícita de OceanBase.

Conversión implícita de OceanBase

Los valores de los tipos de datos biginty datetimeno se pueden comparar directamente. Es necesario convertir intprimero al tipo de tiempo. Esta es la llamada conversión implícita, por lo que la forma en que OceanBase se convierte aquí es muy importante.

  • intLa conversión de tipo al tipo de hora reconocido por OceanBase (es decir, OBTime) no utiliza from_unixtimeesta función, sino que utiliza la propia lógica interna de OceanBase.

  • Involucrado en el código fuente int, la lógica de conversión implícita doublede stringtipo es la siguiente:

int_to_datetime

////////////////////////////////
// int / double / string -> datetime / date / time / year.
int ObTimeConverter::int_to_datetime(int64_t int_part, int64_t dec_part,
                                     const ObTimeConvertCtx &cvrt_ctx, int64_t &value,
                                     const ObDateSqlMode date_sql_mode)
{
  int ret = OB_SUCCESS;
  dec_part = (dec_part + 500) / 1000;
  if (0 == int_part) {
    value = ZERO_DATETIME;
  } else {
    ObTime ob_time(DT_TYPE_DATETIME);
    ObDateSqlMode local_date_sql_mode = date_sql_mode;
    if (cvrt_ctx.is_timestamp_) {
      local_date_sql_mode.allow_invalid_dates_ = false;
    }
    if (OB_FAIL(int_to_ob_time_with_date(int_part, ob_time, false, local_date_sql_mode))) {
      LOG_WARN("failed to convert integer to datetime", K(ret));
    } else if (OB_FAIL(ob_time_to_datetime(ob_time, cvrt_ctx, value))) {
      LOG_WARN("failed to convert datetime to seconds", K(ret));
    }
  }
  value += dec_part;
  if (OB_SUCC(ret) && !is_valid_datetime(value)) {
    ret = OB_DATETIME_FUNCTION_OVERFLOW;
    LOG_WARN("datetime filed overflow", K(ret), K(value));
  }
  return ret;
}

Finalmente llamó int_to_ob_time_with_date:

////////////////////////////////
// int / uint / string -> ObTime / ObInterval <- datetime / date / time.

int ObTimeConverter::int_to_ob_time_with_date(int64_t int64, ObTime &ob_time, bool is_dayofmonth,
                                              const ObDateSqlMode date_sql_mode)
{
  int ret = OB_SUCCESS;
  int32_t *parts = ob_time.parts_;
  if (is_dayofmonth && 0 == int64) {
    parts[DT_SEC]  = 0;
    parts[DT_MIN]  = 0;
    parts[DT_HOUR] = 0;
    parts[DT_MDAY] = 0;
    parts[DT_MON]  = 0;
    parts[DT_YEAR] = 0;
  } else if (int64 < power_of_10[2]) {
    ret = OB_INVALID_DATE_VALUE;
    LOG_WARN("datetime integer is out of range", K(ret), K(int64));
  } else if (int64 < power_of_10[8]) {
    // YYYYMMDD.
    parts[DT_MDAY]  = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2];
    parts[DT_MON]  = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2];
    parts[DT_YEAR] = static_cast<int32_t>(int64 % power_of_10[4]);
  } else if (int64 / power_of_10[6] < power_of_10[8]) {
    // YYYYMMDDHHMMSS.
    parts[DT_SEC]  = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2];
    parts[DT_MIN]  = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2];
    parts[DT_HOUR] = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2];
    parts[DT_MDAY] = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2];
    parts[DT_MON]  = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2];
    parts[DT_YEAR] = static_cast<int32_t>(int64 % power_of_10[4]);
  } else {
    ret = OB_INVALID_DATE_VALUE;
    LOG_WARN("datetime integer is out of range", K(ret), K(int64));
  }
  if (OB_SUCC(ret)) {
    apply_date_year2_rule(parts[0]);
    if (OB_FAIL(validate_datetime(ob_time, is_dayofmonth, date_sql_mode))) {
      LOG_WARN("datetime is invalid or out of range", K(ret), K(int64));
    } else if (ZERO_DATE != parts[DT_DATE]) {
      parts[DT_DATE] = ob_time_to_date(ob_time);
    }
  }
  return ret;
}

El código anterior representa el formato que OceanBase solo puede reconocer:

  • AAAAMMDD
  • AAAAMMDDHHMMSS

Una vez que no esté en el formato anterior, se generará un error. Como puede ver en la figura a continuación, solo 20230816y 20230819111111son elegibles.

causas del problema

Problema 3: Los resultados detectados por SELECT no son los esperados

bigintUna "comparación" con datetimeel tipo implica una conversión implícita que conduce a resultados impredecibles.

  • select * from renzy where EXPIRE_AT < CURRENT_TIMESTAMP; El valor en EXPIRE_ATes bigintde tipo y el valor es 1716040750.
  • Si el valor 1716040750no puede coincidir con el formato especificado, se lanzará int_to_ob_time_with_dateuna alarma " el número entero de fecha y hora está fuera de rango ", que también se puede confirmar en el registro.
  • Aquí, se debe generar un error cuando se ejecuta SQL, y el resultado no se debe mostrar, pero para que el OB sea compatible con MySQL, se selecciona el valor "incorrecto" para que se envíe.
    • ¿Por qué MySQL no informa de un error? Después de que la conversión falla en MySQL, en realidad habrá un valor predeterminado El valor predeterminado utilizado para la falla de conversión del comportamiento de OB mencionado anteriormente debe ser compatible con MySQL, por lo que se cumple la condición 0y se muestra el resultado.WHERE0 < 1686042749SELECT

Pregunta 1: ¿Por qué la instrucción UPDATE puede arrojar un error?

  • Debido a que OB habilita SQL_MODEel modo estricto de forma predeterminada, si se produce una conversión implícita y la conversión falla (se usa el valor predeterminado), el modo estricto de OB tiene una capa de protección en comparación con MySQL y se prohibirá la ejecución de SQL.
  • MySQL SQL_MODEpuede ejecutar "éxito" en modo estricto.

Pregunta 2: la clave principal en la condición WHERE de la instrucción UPDATE coincide con un valor inexistente sin informar un error

  • UPDATESe utiliza el operador table get, y después de que la consulta equivalente no encuentre el resultado, no es necesario aplicar filterlas siguientes condiciones. Esto explica el fracaso esporádico de los equipos de proyecto.

La última pregunta: ¿Por qué ACTUALIZAR informa directamente un error, pero SELECCIONAR puede encontrar un "valor incorrecto"?

Este error solo se arroja al error obs.log.

Mi conjetura aquí es que UPDATEel modo estricto solo se seguirá cuando, pero SELECTno es necesario seguirlo, de acuerdo con el enlace de llamada mencionado anteriormente:

  • ACTUALIZAR: common_int_datetime -> int_to_datetime -> int_to_ob_time_with_date
  • SELECCIONE: int_to_datetime -> int_to_ob_time_with_date

UPDATEHay más capas superiores common_int_datetimey la entrada de llamada es diferente. De acuerdo con este método, se supone aproximadamente que es SQL_MODEcausado por el modo estricto de .

Verifique la conjetura, parece que SQL_MODEdespués de borrar, UPDATEse puede insertar con éxito.

MySQL [test]> select @@sql_mode;
+--------------------------------------------+
| @@sql_mode                        |
+--------------------------------------------+
| STRICT_ALL_TABLES,NO_ZERO_IN_DATE |
+--------------------------------------------+
1 row in set (0.01 sec)

MySQL [test]> update renzy  set at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:[email protected]'  where id = '1'  and (expire_at < CURRENT_TIMESTAMP  or order_id = '0:[email protected]');
ERROR 1292 (22007): Incorrect value

MySQL [test]> set sql_mode='';
Query OK, 0 rows affected (0.01 sec)

MySQL [test]> update renzy  set at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:[email protected]'  where id = '1'  and (expire_at < CURRENT_TIMESTAMP  or order_id = '0:[email protected]');
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Puede haber un problema con mi conjetura, pero mi capacidad personal es limitada y todos pueden corregir y discutir juntos.

en conclusión

  1. Deje que el equipo del proyecto del cliente reescriba la lógica SQL.

    • EXPIRE_AT < CURRENT_TIMESTAMP cambiado a

    • EXPIRE_AT < unix_timestamp(CURRENT_TIMESTAMP)

  2. Haga hincapié en la especificación de SQL y evite las conversiones implícitas.

Acerca de SQLE

El SQLE de la comunidad de código abierto de Akson es una herramienta de auditoría de SQL para usuarios y administradores de bases de datos, que admite la auditoría de escenarios múltiples, admite procesos en línea estandarizados, admite de forma nativa la auditoría de MySQL y tiene tipos de bases de datos escalables.

SQLE obtener

tipo DIRECCIÓN
Repositorio https://github.com/actiontech/sqle
documento https://actiontech.github.io/sqle-docs/
lanzamiento de noticias https://github.com/actiontech/sqle/releases
Documentación de desarrollo del complemento de auditoría de datos https://actiontech.github.io/sqle-docs-cn/3.modules/3.7_auditplugin/auditplugin_development.html
Cliente iQIYI "White" TV, cargando a toda velocidad en segundo plano. Deepin usa Asahi Linux para adaptarse a Apple M1 Threads . Lista de julio: C ++ está a punto de superar a C, JavaScript ingresará a Top6 Visual Studio Code 1.80 lanzado, admite la función de imagen de terminal El tráfico de ChatGPT cae un 10 % en segundo plano y en el front-end que sufren CURD durante mucho tiempo, y la clasificación de la base de datos de julio de Koala Form: Oracle se dispara, una vez más abrió las clasificaciones de participación de mercado de navegadores de escritorio globales, Safari continuó sentado firmemente en el segundo lugar
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/actiontechoss/blog/10087844
Recomendado
Clasificación