If the Insert statement you are running comes from a single sql statement, then you wouldn't need to manually lock any records?
eg:BEGIN TRANSACTION [Tran1]
BEGIN TRY
Insert into invoice_table(id,invoice_company,col2,col3,....)
select (select max(id)+1 from invoice_table) as id
,'Company XYZ' as invoice_company
,col2
COMMIT TRANSACTION [Tran1]
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION [Tran1]
PRINT 'Some error'
THROW
END CATCH
However the requirement for gap free monotonically increasing numbers would mean you are limiting the scalability of the transaction, since, max+1 logic in a multi user scenario would mean that users would have to wait until the previous transaction is complete to get their inserts to work. This kind of problem can be overcome using sequences, if the requirement for gap free is not such a big deal. As it is by using a database if your actions are coded to be atomic(either the invoice module would cause an insert to the table or never gets into the database and throws an exception), the you can be sure that you wont have any missing invoices from your application when the application calls the "add_invoice" stored procedure..