¿Es 'length () IS NULL' equivalente y más rápido que 'IS NULL' para BLOB?

Tengo una database SQLite de ~ 90 MB en un disco SSD que consiste principalmente en files adjuntos de posts, incluido un contenido de columna BLOB, que almacena los datos del file adjunto binary.

Ahora encontré que la siguiente consulta

SELECT message_id FROM attachments WHERE length(content) IS NULL; 

es 500 veces más rápido (0.5ms vs. 250ms) que el original

 SELECT message_id FROM attachments WHERE content IS NULL; 

¿Es cierto que ambas consultas son equivalentes?

información adicional

  1. No hay índices involucrados aparte del autoindex.
  2. No es el almacenamiento en caching. El resultado se puede reproducir un número ilimitado de veces en cualquier order desde cualquier cantidad de processs SQLite.

En SQLite, la longitud y el tipo de cada valor de columna se almacenan al comienzo de la fila . Esto permite optimizar las funciones length() y typeof() para evitar cargar el valor real.

El operador IS NULL no tiene dicha optimization (aunque sería posible implementarla).

Hice una secuencia de commands para comparar ambas funciones. length(x) IS NULL es más rápido a less que tenga valores NULL su mayoría.

Resultados:

  • 50% alternan entre datos aleatorios y nulos:
    • IS NULL : 11.343180236999842
    • length(x) IS NULL NULA: 7.824154090999855
  • Completamente blobs, sin nulos:
    • IS NULL : 15.019244787999924
    • length(x) IS NULL NULA: 7.527420233999919
  • Totalmente nulos, sin manchas:
    • IS NULL : 6.184766045999822
    • length(x) IS NULL NULA: 6.448342310000044

Secuencia de commands de testing:

 import sqlite3 import timeit conn = sqlite3.connect("test.db") c = conn.cursor() c.execute("DROP TABLE IF EXISTS test") c.execute("CREATE TABLE test (data BLOB)") for i in range(10000): # Modify this to change data if i % 2 == 0: c.execute("INSERT INTO test(data) VALUES (randomblob(1024))") else: c.execute("INSERT INTO test(data) VALUES (NULL)") def timeit_isnull(): c.execute("SELECT data IS NULL AS dataisnull FROM test") c.fetchall() def timeit_lenisnull(): c.execute("SELECT length(data) IS NULL AS dataisnull FROM test") c.fetchall() print(timeit.timeit(timeit_isnull, number=1000)) print(timeit.timeit(timeit_lenisnull, number=1000)) 

En realidad, utilizar LENGTH(some_blob_content) instruye al server MySQL 5.5 y superior para eludir el escaneo de filas, esta es la razón para un mejor performance de su consulta, porque los datos se leen directamente desde la tabla de metadatos.

EDITAR:

En SQLite durante el process parte INSERT y SELECT , el contenido completo de cada fila en la database se codifica como un solo BLOB . Mira aquí para más detalles.

También para un valor de cadena X, la function de length(X) devuelve el número de caracteres ( no bytes ) en X antes del primer carácter NULL . Como las cadenas de SQLite normalmente no contienen caracteres NULL , la function de length(X) generalmente devolverá el número total de caracteres en la cadena X. Para un valor de blob X, la length(X) devuelve el número de bytes en la burbuja. Si X es NULL , la length(X) es NULL. Si X es numérico, la length(X) devuelve la longitud de una representación de cadena de X.

La cita anterior de la documentation sugiere que, para los valores de blob , el procedimiento de lectura de su longitud es el mismo que en MySQL, es decir, desde la tabla de metadatos.