You've got two separate unique indexes on that table, and you're checking one, then inserting into the other, then inserting into the one you initially checked. But you're not checking the first one with a restrictive lock. So two sessions can both check the stats_ip index, discover that no row exists and attempt the insert. The deadlock prevents them from both succeeding. Anyway the solution here is to perform the first lookup with a more restrictive lock, and one that locks empty ranges. Like this:
create procedure GetOrInsertStatId @ip bigint
as
begin
set xact_abort on;
set nocount on;
begin transaction
declare @stat_id int =
(
select stat_id
from stat with (updlock, serializable)
where [stats_ip] = @ip
)
if @stat_id is null
begin
insert into stat(stats_ip) values (@ip)
set @stat_id = SCOPE_IDENTITY()
end
select @stat_id stat_id
commit transaction
end