En primer lugar, si utiliza de forma predeterminada el motor de almacenamiento InnoDB de MySQL, entonces no hay forma de que pueda actualizar los datos sin bloqueos de fila, excepto establecer el nivel de aislamiento de la transacción en LECTURA NO COMPROMETIDA ejecutando
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Sin embargo, no creo que el comportamiento de la base de datos sea el esperado, ya que en este caso se permite la lectura sucia. READ UNCOMMITTED rara vez es útil en la práctica.
Para complementar la respuesta de @Tim, es una buena idea tener un índice único en la columna utilizada en la cláusula where. Sin embargo, tenga en cuenta también que no hay garantía absoluta de que el optimizador finalmente elija dicho plan de ejecución utilizando el índice creado. Puede funcionar o no, según el caso.
Para su caso, lo que podría hacer es dividir la transacción larga en varias transacciones cortas. En lugar de actualizar millones de filas de una sola vez, sería mejor escanear solo miles de filas cada vez. Los bloqueos X se liberan cuando cada transacción corta se confirma o revierte, lo que brinda a las actualizaciones simultáneas la oportunidad de continuar.
Por cierto, supongo que su lote tiene menor prioridad que los otros procesos en línea, por lo que podría programarse fuera de las horas pico para minimizar aún más el impacto.
PD El bloqueo IX no está en el registro en sí, sino que está adjunto al objeto de la tabla de mayor granularidad. E incluso con el nivel de aislamiento de transacciones de LECTURA REPETIBLE, no hay bloqueo de brecha cuando la consulta usa un índice único.