Prima di tutto, se hai impostato come predefinito il motore di archiviazione InnoDB di MySQL, non c'è modo per aggiornare i dati senza blocchi di riga se non impostando il livello di isolamento della transazione su READ UNCOMMITTED eseguendo
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Tuttavia, non penso che il comportamento del database sia quello che ti aspetti poiché in questo caso è consentita la lettura sporca. READ UNCOMMITTED è raramente utile nella pratica.
Per completare la risposta di @Tim, è davvero una buona idea avere un indice univoco sulla colonna utilizzata nella clausola where. Tuttavia, tieni anche presente che non vi è alcuna garanzia assoluta che l'ottimizzatore alla fine sceglierà tale piano di esecuzione utilizzando l'indice creato. Potrebbe funzionare o meno, a seconda dei casi.
Nel tuo caso, quello che potresti fare è dividere la transazione lunga in più transazioni corte. Invece di aggiornare milioni di righe in un colpo solo, sarebbe meglio scansionare solo migliaia di righe ogni volta. I blocchi X vengono rilasciati quando ogni transazione breve esegue il commit o il rollback, dando agli aggiornamenti simultanei l'opportunità di andare avanti.
A proposito, presumo che il tuo batch abbia una priorità inferiore rispetto agli altri processi online, quindi potrebbe essere programmato fuori dalle ore di punta per ridurre ulteriormente l'impatto.
PS Il blocco IX non è sul record stesso, ma è collegato all'oggetto tabella con granularità superiore. E anche con il livello di isolamento della transazione REPEATABLE READ, non c'è blocco del gap quando la query utilizza un indice univoco.