Usando MySQL 5.6 con el motor de almacenamiento InnoDB para la mayoría de las tablas. El tamaño de la agrupación de almacenamiento intermedio de InnoDB es de 15 GB y los índices Innodb DB + son de alrededor de 10 GB. El servidor tiene 32 GB de RAM y ejecuta Cent OS 7 x64.
Tengo una gran tabla que contiene más de 10 millones de registros.
Recibo un archivo de volcado actualizado de un servidor remoto cada 24 horas. El archivo está en formato csv. No tengo control sobre ese formato. El archivo es ~ 750 MB. Intenté insertar datos en una tabla MyISAM fila por fila y me tomó 35 minutos.
Necesito tomar solo 3 valores por línea de 10-12 del archivo y actualizarlo en la base de datos.
¿Cuál es la mejor manera de lograr algo como esto?
Necesito hacer esto diariamente.
Actualmente Flow es así:
- mysqli_begin_transaction
- Leer archivo de volcado línea por línea
- Actualice cada registro línea por línea.
- mysqli_commit
Las operaciones anteriores tardan entre 30 y 40 minutos en completarse y, mientras lo hago, hay otras actualizaciones en curso que me dan
Tiempo de espera de bloqueo excedido; intente reiniciar la transacción
Actualización 1
carga de datos en una nueva tabla usando LOAD DATA LOCAL INFILE
. En MyISAM tardó 38.93 sec
mientras que en InnoDB tardó 7 min 5.21 sec. Entonces hice:
UPDATE table1 t1, table2 t2
SET
t1.field1 = t2.field1,
t1.field2 = t2.field2,
t1.field3 = t2.field3
WHERE t1.field10 = t2.field10
Query OK, 434914 rows affected (22 hours 14 min 47.55 sec)
Actualización 2
misma actualización con consulta de unión
UPDATE table1 a JOIN table2 b
ON a.field1 = b.field1
SET
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4
(14 hours 56 min 46.85 sec)
Aclaraciones de las preguntas en los comentarios:
- Alrededor del 6% de las filas de la tabla serán actualizadas por el archivo, pero a veces puede llegar hasta el 25%.
- Hay índices en los campos que se actualizan. Hay 12 índices en la tabla y 8 índices incluyen los campos de actualización.
- No es necesario hacer la actualización en una transacción. Puede llevar tiempo, pero no más de 24 horas. Estoy tratando de hacerlo en 1 hora sin bloquear toda la tabla, ya que luego tengo que actualizar el índice de esfinge que depende de esta tabla. No importa si los pasos duran más, siempre que la base de datos esté disponible para otras tareas.
- Podría modificar el formato csv en un paso de preproceso. Lo único que importa es una actualización rápida y sin bloqueo.
- La tabla 2 es MyISAM. Es la tabla recién creada del archivo csv que utiliza el archivo de datos de carga. El tamaño del archivo MYI es de 452 MB. La tabla 2 está indexada en la columna campo1.
- MYD de la tabla MyISAM es 663MB.
Actualización 3:
Aquí hay más detalles sobre ambas tablas.
CREATE TABLE `content` (
`hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`og_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`keywords` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`files_count` smallint(5) unsigned NOT NULL DEFAULT '0',
`more_files` smallint(5) unsigned NOT NULL DEFAULT '0',
`files` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
`category` smallint(3) unsigned NOT NULL DEFAULT '600',
`size` bigint(19) unsigned NOT NULL DEFAULT '0',
`downloaders` int(11) NOT NULL DEFAULT '0',
`completed` int(11) NOT NULL DEFAULT '0',
`uploaders` int(11) NOT NULL DEFAULT '0',
`creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`upload_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`last_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`vote_up` int(11) unsigned NOT NULL DEFAULT '0',
`vote_down` int(11) unsigned NOT NULL DEFAULT '0',
`comments_count` int(11) NOT NULL DEFAULT '0',
`imdb` int(8) unsigned NOT NULL DEFAULT '0',
`video_sample` tinyint(1) NOT NULL DEFAULT '0',
`video_quality` tinyint(2) NOT NULL DEFAULT '0',
`audio_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
`subtitle_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
`verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
`uploader` int(11) unsigned NOT NULL DEFAULT '0',
`anonymous` tinyint(1) NOT NULL DEFAULT '0',
`enabled` tinyint(1) unsigned NOT NULL DEFAULT '0',
`tfile_size` int(11) unsigned NOT NULL DEFAULT '0',
`scrape_source` tinyint(1) unsigned NOT NULL DEFAULT '0',
`record_num` int(11) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`record_num`),
UNIQUE KEY `hash` (`hash`),
KEY `uploaders` (`uploaders`),
KEY `tfile_size` (`tfile_size`),
KEY `enabled_category_upload_date_verified_` (`enabled`,`category`,`upload_date`,`verified`),
KEY `enabled_upload_date_verified_` (`enabled`,`upload_date`,`verified`),
KEY `enabled_category_verified_` (`enabled`,`category`,`verified`),
KEY `enabled_verified_` (`enabled`,`verified`),
KEY `enabled_uploader_` (`enabled`,`uploader`),
KEY `anonymous_uploader_` (`anonymous`,`uploader`),
KEY `enabled_uploaders_upload_date_` (`enabled`,`uploaders`,`upload_date`),
KEY `enabled_verified_category` (`enabled`,`verified`,`category`),
KEY `verified_enabled_category` (`verified`,`enabled`,`category`)
) ENGINE=InnoDB AUTO_INCREMENT=7551163 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=FIXED
CREATE TABLE `content_csv_dump_temp` (
`hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`category_id` int(11) unsigned NOT NULL DEFAULT '0',
`uploaders` int(11) unsigned NOT NULL DEFAULT '0',
`downloaders` int(11) unsigned NOT NULL DEFAULT '0',
`verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
y aquí está la consulta de actualización que actualiza la content
tabla usando datos decontent_csv_dump_temp
UPDATE content a JOIN content_csv_dump_temp b
ON a.hash = b.hash
SET
a.uploaders = b.uploaders,
a.downloaders = b.downloaders,
a.verified = b.verified
actualización 4:
todas las pruebas anteriores se realizaron en la máquina de prueba, pero ahora hice las mismas pruebas en la máquina de producción y las consultas son muy rápidas.
mysql> UPDATE content_test a JOIN content_csv_dump_temp b
-> ON a.hash = b.hash
-> SET
-> a.uploaders = b.uploaders,
-> a.downloaders = b.downloaders,
-> a.verified = b.verified;
Query OK, 2673528 rows affected (7 min 50.42 sec)
Rows matched: 7044818 Changed: 2673528 Warnings: 0
Pido disculpas por mi error. Es mejor usar join en lugar de cada actualización de registro. ahora estoy tratando de mejorar mpre usando el índice sugerido por rick_james, se actualizará una vez que se realice el benchmarking.
INDEX(field2, field3, field4)
(en cualquier orden)? Por favor muéstranosSHOW CREATE TABLE
.UPDATEs
. Díganos exactamente cómo se ve la declaración directa para actualizar la tabla a partir de los datos csv. Entonces podremos ayudarlo a diseñar una técnica que cumpla con sus requisitos.update
, y por favor revise la pregunta actualizada. GraciasRespuestas:
Según mi experiencia, usaría LOAD DATA INFILE para importar su archivo CSV.
Ejemplo que encontré en Internet Ejemplo de carga de datos . Probé este ejemplo en mi caja y funcionó bien
Tabla de ejemplo
Archivo CSV de ejemplo
Declaración de importación que se ejecutará desde la consola MySQL
Resultado
IGNORE simplemente ignora la primera línea que son los encabezados de columna.
Después de IGNORE, estamos especificando las columnas (omitiendo la columna2) para importar, que coincide con uno de los criterios en su pregunta.
Aquí hay otro ejemplo directamente de Oracle: ejemplo LOAD DATA INFILE
Esto debería ser suficiente para comenzar.
fuente
A la luz de todas las cosas mencionadas, parece que el cuello de botella es la unión en sí.
ASPECTO # 1: Tamaño del búfer de unión
Con toda probabilidad, su join_buffer_size es probablemente demasiado bajo.
De acuerdo con la documentación de MySQL sobre cómo MySQL utiliza la caché de búfer de unión
Siendo este el caso, haga que las claves del búfer de unión permanezcan en la RAM.
Tiene 10 millones de filas por 4 bytes para cada clave. Eso es alrededor de 40 millones.
Intenta aumentarlo en la sesión a 42M (un poco más grande que 40M)
Si esto funciona, agregue esto a
my.cnf
No es necesario reiniciar mysqld para nuevas conexiones. Solo corre
ASPECTO # 2: Operación de unión
Puede manipular el estilo de la operación de combinación modificando el optimizador
De acuerdo con la documentación de MySQL sobre las uniones de acceso de bloque anidado y de bloque bloqueado
Esta misma página recomienda hacer esto:
ASPECTO # 3: Escribir actualizaciones en el disco (OPCIONAL)
La mayoría se olvida de aumentar innodb_write_io_threads para escribir páginas sucias fuera del grupo de búferes más rápido.
Tendrá que reiniciar MySQL para este cambio
DARLE UNA OPORTUNIDAD !!!
fuente
CREATE TABLE
que coincide con el CSVLOAD DATA
en esa mesaUPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
DROP TABLE csv_table;
El paso 3 será mucho más rápido que fila por fila, pero seguirá bloqueando todas las filas de la tabla durante un período de tiempo no trivial. Si este tiempo de bloqueo es más importante que el tiempo que lleva todo el proceso, entonces ...
Si nada más está escribiendo en la mesa, entonces ...
CREATE TABLE
que coincide con el CSV; sin índices excepto lo que se necesitaJOIN
en elUPDATE
. Si es único, hazloPRIMARY KEY
.LOAD DATA
en esa mesareal_table
anew_table
(CREATE ... SELECT
)UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
RENAME TABLE real_table TO old, new_table TO real_table;
DROP TABLE csv_table, old;
El paso 3 es más rápido que la actualización, especialmente si se dejan índices innecesarios.
El paso 5 es "instantáneo".
fuente
pt-online-schema-digest
; se encarga de tales problemas a través de aTRIGGER
.LOAD DATA
. Agregar índices innecesarios es costoso (en el tiempo).AUTO_INCREMENT
, y luego agrupando 1K filas a la vez en función de la PK. Pero necesito ver todos los requisitos y el esquema de la tabla antes de intentar detallar los detalles.PRIMARY index
, pero aunque fragmentar en 50k usando la consulta de pedido lleva más tiempo, ¿sería mejor si creo un incremento automático? y configurarlo comoPRIMARY index
?Usted ha dicho:
Muchas de estas declaraciones pueden ser contradictorias. Por ejemplo, actualizaciones grandes sin bloquear la tabla. O evitar condiciones de carrera sin usar una transacción gigante.
Además, dado que su tabla está muy indexada, tanto las inserciones como las actualizaciones pueden ser lentas.
Evitar condiciones de carrera
Si puede agregar una marca de tiempo actualizada a su tabla, puede resolver las condiciones de la carrera y evitar el registro de medio millón de actualizaciones en una sola transacción.
Esto le permite realizar actualizaciones línea por línea (como lo hace actualmente), pero con confirmación automática o lotes de transacciones más razonables.
Evita las condiciones de carrera (al actualizar línea por línea) realizando una comprobación de que no se haya producido una actualización posterior (
UPDATE ... WHERE pk = [pk] AND updated < [batchfile date]
)Y, lo que es más importante, esto le permite ejecutar actualizaciones paralelas .
Correr lo más rápido posible: paralelización
Con esta marca de tiempo, compruebe ahora en su lugar:
mysql
ejecute cada archivo sql.(p. ej., para
bash
versplit
y encontrarxargs -P
formas de ejecutar fácilmente un comando en paralelo de muchas maneras. El grado de paralelismo depende de cuántos hilos esté dispuesto a dedicar a la actualización )fuente
Grandes actualizaciones están vinculadas a E / S. Yo sugeriría:
fuente
Para el
UPDATE
que corra rápido, necesitaPuede estar en cualquier mesa. Los tres campos pueden estar en cualquier orden.
Eso facilitará la
UPDATE
posibilidad de unir rápidamente las filas entre las dos tablas.Y haga que los tipos de datos sean los mismos en las dos tablas (ambas
INT SIGNED
o ambasINT UNSIGNED
).fuente
EXPLAIN UPDATE ...;
.