Да, можно использовать один и тот же запрос с разными значениями, но при большом количестве строк производительность Вас может огорчить.
А что если Вы создадите массив, куда будете писать все значения? Заполнять его будете одновременным с формированием строки запроса. Например, так:
$sql = 'INSERT INTO `customers` (`id`, `name`, `email`) VALUES ';
$binds = [];
$index = 0;
$count = count($customers);
foreach($customers as $customer) {
if ($customer->id) {
$sql .= '(?, ?, ?)';
$binds[] = $customer->id;
} else {
$sql .= '(NULL, ?, ?)';
}
if (++$index != $count) {
$sql .= ', ';
}
$binds[] = $customer->name;
$binds[] = $customer->email;
}
$sql .= ' ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `email` = VALUES(`email`)';
$query = $mysqli->prepare($sql);
foreach($binds as $bind) {
$query->bind_param("s", $bind);
}