//
Search
Duplicate
๐Ÿ•“

๋ฐ๋“œ๋ฝ ์›์ธ๊ณผ ํ•ด๊ฒฐ๋ฐฉ์•ˆ ๊ณ ๋ฏผ(๋น„๊ด€์  ๋ฝ, ๋‚™๊ด€์  ๋ฝ)

์ƒ์„ฑ์ผ
2023/02/03
ํƒœ๊ทธ
Spring
Transaction
MySQL
JPA
๋ชฉ์ฐจ

๋ฐ๋“œ๋ฝ์ด ๋ฐœ์ƒํ•œ ์ด์œ 

์ €ํฌ ์„œ๋น„์Šค์—์„œ๋Š” ํˆฌํ‘œ/์•ฝ์†์žก๊ธฐ ๋ชฉ๋ก ์กฐํšŒ์‹œ ํˆฌํ‘œ ๋˜๋Š” ์•ฝ์†์žก๊ธฐ๋ฅผ ๋งˆ์นœ ์ธ์›์˜ ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด์—๋Š” ์—ฐ๊ด€ ๊ด€๊ณ„๋กœ ๋งคํ•‘๋œ ๊ฒฐ๊ณผ ํ…Œ์ด๋ธ”์—์„œ ํ•ด๋‹น ํˆฌํ‘œ ๋˜๋Š” ์•ฝ์†์žก๊ธฐ์˜ id ๋กœ ์กฐํšŒํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ์˜ ์ˆ˜๋ฅผ ์„ธ๋Š” ๋กœ์ง์ด์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ์Œ“์ด๋ฉด ์Œ“์ผ์ˆ˜๋ก ์กฐํšŒ ์„ฑ๋Šฅ์ด ์ €ํ•˜๊ฐ€ ๋˜๋Š” ๋‹จ์ ์„ ๋ฐœ๊ฒฌํ–ˆ๊ณ , ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํˆฌํ‘œ/์•ฝ์†์žก๊ธฐ๋ฅผ ๋งˆ์นœ ์ธ์›์˜ ์ˆ˜๋ฅผ update ํ•˜๋Š” selected_count ์นผ๋Ÿผ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐ˜์ •๊ทœํ™”๋ฅผ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋ฐ˜์ •๊ทœํ™”๋ฅผ ํ•œ ํˆฌํ‘œ/์•ฝ์†์žก๊ธฐ ์ง„ํ–‰ ์‹œ์˜ ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
1.
ํˆฌํ‘œ/์•ฝ์†์žก๊ธฐ ์กฐํšŒ
2.
ํˆฌํ‘œ/์•ฝ์†์žก๊ธฐ ์„ ํƒ ๊ฒฐ๊ณผ ๋ ˆ์ฝ”๋“œ ์‚ฝ์ž…
3.
ํˆฌํ‘œ/์•ฝ์†์žก๊ธฐ selected_count + 1 ์ˆ˜์ •
์ฒ˜์Œ ์ด๋Ÿฐ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ตฌ์„ฑํ•  ๋‹น์‹œ์—๋Š” ์ด ๊ณผ์ •์—์„œ ๋™์‹œ์„ฑ๊ณผ ๊ด€๋ จ๋œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋„˜์–ด๊ฐ”์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ• ๊นŒ์š”?

๋ฐ˜์ •๊ทœํ™” ํ•˜๋ฉด์„œ ์ƒ๊ฒจ๋‚  ์ˆ˜ ์žˆ๋Š” ๋ฐ๋“œ๋ฝ ๋ฌธ์ œ

์ €ํฌ ๋กœ์ง์€ ํˆฌํ‘œ/์•ฝ์†์žก๊ธฐ ์ง„ํ–‰ ์‹œ ํ•ด๋‹น ๋ฉค๋ฒ„๊ฐ€ ์•„์ง ์ง„ํ–‰ํ•˜์ง€ ์•Š์€ ๋ฉค๋ฒ„๋ผ๋ฉด selected_count ๋ฅผ +1 ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด JPA ๋Š” ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์ข…๋ฃŒ ์‹œ์  ๋˜๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ flush๊ฐ€ ์ผ์–ด๋‚˜๋Š” ์‹œ์ ์— ์—”ํ‹ฐํ‹ฐ์˜ ์Šค๋ƒ…์ƒท์„ ๋น„๊ตํ•˜๋Š” ๋”ํ‹ฐ ์ฒดํ‚น์„ ํ•˜๊ณ  ๋ณ€๊ฒฝ๋œ ์ปฌ๋Ÿผ์ด ์žˆ์œผ๋ฉด ๋ณ€๊ฒฝ๋œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด update ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ง์ ‘ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  JPA ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ์ตœ๋Œ€ํ•œ ๋„๋ฉ”์ธ ๊ฐ์ฒด ๋‚ด๋ถ€์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋งŒ๋“ค์–ด ์‘์ง‘๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ์ง€์ 

์œ„์˜ 1, 2, 3 ๋กœ์ง์€ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  2๋ฒˆ ๋กœ์ง์—์„œ ์„ ํƒ ๊ฒฐ๊ณผ๋ฅผ ์‚ฝ์ž…ํ•  ๋•Œ์—๋Š”, ์ฃผ ํ…Œ์ด๋ธ”์˜ ์™ธ๋ž˜ํ‚ค๋ฅผ ํ•จ๊ป˜ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์œผ๋กœ ๊ธฐ์ธํ•˜์—ฌ ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ €ํฌ๊ฐ€ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” MySQL ์˜ Inno DB ์˜ ํŠน์„ฑ์ƒ, ์—ฐ๊ด€๊ด€๊ณ„์— ์žˆ๋Š” ํ…Œ์ด๋ธ”์— ๋ ˆ์ฝ”๋“œ ์‚ฝ์ž… ์‹œ ์—ฐ๊ด€๊ด€๊ณ„์— ์žˆ๋Š” ๋ ˆ์ฝ”๋“œ์— ๊ณต์œ ๋ฝ์„ ์žก์Šต๋‹ˆ๋‹ค.
โ€ข
์•ฝ์†์žก๊ธฐ
โ€ข
์•ฝ์†์žก๊ธฐ ์„ ํƒ ๊ฒฐ๊ณผ
id
title
selected_count
1
ํšŒ์‹ ๋‚ ์งœ
0
2
์Šคํ„ฐ๋”” ์‹œ๊ฐ„
0
id
appointment_id
1
1
2
1
์•ฝ์†์žก๊ธฐ ์„ ํƒ ๊ฒฐ๊ณผ์— ๋ ˆ์ฝ”๋“œ๋ฅผ ์‚ฝ์ž…ํ•  ๋•Œ ์™ธ๋ž˜ํ‚ค(appointment_id 1)๋„ ์ €์žฅํ•˜๋Š”๋ฐ, ์ด ์™ธ๋ž˜ํ‚ค์— ์žˆ๋Š” ๋ ˆ์ฝ”๋“œ์ธ ์•ฝ์†์žก๊ธฐ์˜ id๊ฐ€ 1์ธ ๋ ˆ์ฝ”๋“œ์— ๊ณต์œ ๋ฝ์„ ํš๋“ํ•ฉ๋‹ˆ๋‹ค.
start transaction; insert into appointment_available_time values (null, 1); select * from performance_schema.data_locks;
SQL
๋ณต์‚ฌ
lock_mode ์ปฌ๋Ÿผ์„ ๋ณด๋ฉด S ๋ผ๋Š” ๊ณต์œ ๋ฝ(Shared lock)๊ณผ, REC_NOT_GAP ์ด๋ผ๋Š” ๊ฐญ๋ฝ์ด ์•„๋‹Œ ๋ ˆ์ฝ”๋“œ๋ฝ ์„ ํš๋“ํ•œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ดํ›„ 3๋ฒˆ ๋กœ์ง์—์„œ ์•ฝ์†์žก๊ธฐ์˜ ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ์— selected_count ๋ฅผ +1 ํ•˜๋Š” ์—…๋ฐ์ดํŠธ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ๋Š”๋ฐ update ํ•  ๋•Œ์—๋Š” ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ์— ๋ฐฐํƒ€๋ฝ์„ ํš๋“ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
update appointment set selected_count=1 where id = 1; select * from performance_schema.data_locks;
SQL
๋ณต์‚ฌ
์™ผ์ชฝ์˜ ๊ณต์œ ๋ฝ๊ณผ ๋”๋ถˆ์–ด, X ๋ผ๋Š” ๋ฐฐํƒ€๋ฝ(eXclusive lock) ์„ ํš๋“ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ, ๊ณต์œ ๋ฝ์€ ์กฐํšŒ๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์ˆ˜์ •์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ , ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ๋Š” ๋ฐฐํƒ€๋ฝ ํš๋“์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์—ฌ ํš๋“์ด ๊ฐ€๋Šฅํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋งŒ์•ฝ ๋‘ ๊ฐœ์˜ ํŠธ๋žœ์žญ์…˜์—์„œ ๊ฐ๊ฐ ๊ณต์œ ๋ฝ์„ ํš๋“ ํ›„ ๋ฐฐํƒ€๋ฝ์„ ํš๋“ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”?
๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ์„œ๋กœ ์–ฝํžŒ ์ƒํƒœ์—์„œ ๋ฐฐํƒ€๋ฝ์„ ํš๋“ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜๋ฉด ์„œ๋กœ ๋Œ€๊ธฐ๋งŒ ํ•˜๋Š” ์ƒํƒœ์— ๋น ์ ธ ๋ฐ๋“œ๋ฝ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
์ด ๋ฐ๋“œ๋ฝ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

๋น„๊ด€๋ฝ

๊ฐ€์žฅ ๋จผ์ €, ๊ตฌ์ƒํ•˜๊ธฐ ๊ฐ€์žฅ ์‰ฌ์šด ๋น„๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค.
1๋ฒˆ ๋กœ์ง์ธ ์•ฝ์†์žก๊ธฐ ์กฐํšŒ ์‹œ ๋น„๊ด€์  ๋ฝ์„ ๊ฑธ์–ด์ฃผ๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ์— ๋Œ€ํ•ด ๋ฐฐํƒ€๋ฝ์„ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— A ํŠธ๋žœ์žญ์…˜์—์„œ ๋น„๊ด€์  ๋ฝ์œผ๋กœ ์•ฝ์†์žก๊ธฐ๋ฅผ ์กฐํšŒํ•˜๋ฉด, A ํŠธ๋žœ์žญ์…˜์ด ๋๋‚  ๋•Œ ๊นŒ์ง€ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜๋“ค์€ ๋ฐฐํƒ€๋ฝ ํš๋“๋„, ๊ณต์œ ๋ฝ ํš๋“๋„ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค(๋‹จ ๋ฝ ํš๋“์—†๋Š” ๋‹จ์ˆœ ์กฐํšŒ๋Š” ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค). ํ•œ๋ฒˆ์— ํ•œ ํŠธ๋žœ์žญ์…˜๋งŒ ๋ฝ์„ ์–ป์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๋„, ๋ฐ๋“œ๋ฝ ๋ฌธ์ œ๋„ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค.
์Šคํ”„๋ง์—์„œ๋Š” @Lock ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ๋น„๊ด€์  ๋ฝ๊ณผ ๋‚™๊ด€์  ๋ฝ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
@Query("select a from Appointment a where a.menu.code.code = :appointmentCode") @Lock(LockModeType.PESSIMISTIC_WRITE) Optional<Appointment> findByCodeForUpdate(@Param("appointmentCode") String appointmentCode);
Java
๋ณต์‚ฌ
๊ทธ๋ฆฌ๊ณ  CountDownLatch ๋ฅผ ์ด์šฉํ•˜์—ฌ ์“ฐ๋ ˆ๋“œ 100๊ฐœ๋ฅผ ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.
๋น„๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ์กฐํšŒ ์‹œ์— for update ํ‚ค์›Œ๋“œ๋ฅผ ํ†ตํ•ด ๋ฐฐํƒ€๋ฝ์„ ํš๋“ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์™„๋ฃŒ๋œ ๊ฐฏ์ˆ˜๋ฅผ ์„ธ์–ด๋ณด๋ฉด!
select selected_count, (select count(*) from appointment_available_time) as 'appointment_available_time_count' from appointment;
SQL
๋ณต์‚ฌ
100 ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์‚ฝ์ž…๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹จ, ์ด๋ ‡๊ฒŒ ํ•  ๊ฒฝ์šฐ ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด ํฌ๊ฒŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์œ„์˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ๋กœ์ง์„ ์‹œ์ž‘ํ•˜์ž๋งˆ์ž ํŠธ๋žœ์žญ์…˜์—์„œ ๋ฐฐํƒ€๋ฝ์„ ์–ป๊ธฐ๋•Œ๋ฌธ์— ์ดํ›„์˜ ์“ฐ๋ ˆ๋“œ์—์„œ๋Š” ๊ฐ๊ฐ์˜ ํŠธ๋žœ์žญ์…˜๋“ค์ด ๋จผ์ € ์‹œ์ž‘๋œ ํŠธ๋žœ์žญ์…˜์—์„œ ์ปค๋ฐ‹ ๋˜๋Š” ๋กค๋ฐฑ๋  ๋•Œ๊นŒ์ง€ ๋กœ์ง์„ ์‹คํ–‰ํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋Œ€๊ธฐ ์ƒํƒœ์— ๋น ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ •ํ•ฉ์„ฑ์„ ํ™•์‹คํ•˜๊ฒŒ ๋งž์ถœ ์ˆ˜๋Š” ์žˆ์ง€๋งŒ, ๋™์‹œ์— ์ ‘๊ทผํ•˜๋Š” ํŠธ๋žœ์žญ์…˜์ด ๋งŽ์•„์ง€๋ฉด ๋งŽ์•„์งˆ์ˆ˜๋ก API ์ฝœ์˜ ๋Œ€๊ธฐ ์‹œ๊ฐ„์€ ๋Š˜์–ด๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ํ˜„์—…์—์„œ๋„ ํšจ์œจ์ƒ, ๋น„๊ด€๋ฝ์„ ๊ฑฐ์˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์นœ๊ตฌ์˜ ๋ง์„ ๋“ค์–ด ๋น„๊ด€์  ๋ฝ์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์œผ๋กœ ๊ฒฐ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
๋‚™๊ด€์  ๋ฝ์€ ํ˜„์žฌ ์ƒํƒœ์—์„œ๋Š” ๋ฐ๋“œ๋ฝ์— ๋น ์ง€๋Š” ๊ฒƒ์„ ํ”ผํ•  ์ˆ˜ ์—†๊ธฐ๋•Œ๋ฌธ์— ์ดํ›„์— ๋‹ค๋ฃจ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

foreign key ๋–ผ๊ธฐ

๊ณต์œ ๋ฝ์„ ํš๋“ํ•˜๋Š” ์ด์œ ๋Š” ์™ธ๋ž˜ํ‚ค ์ œ์•ฝ ์กฐ๊ฑด ๋•Œ๋ฌธ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ๋ฐ๋“œ๋ฝ์„ ์œ ๋ฐœ์‹œ์ผฐ๋˜ ๊ณต์œ ๋ฝ ํš๋“ํ•˜๋Š” ๋กœ์ง์„ ์—†์• ๊ธฐ ์œ„ํ•ด ์™ธ๋ž˜ํ‚ค ์ œ์•ฝ์กฐ๊ฑด์„ ์ œ๊ฑฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.
alter table appointment_available_time drop foreign key appointment_available_time_ibfk_1; select * from information_schema.table_constraints where table_name = 'appointment_available_time';
SQL
๋ณต์‚ฌ
์™ธ๋ž˜ํ‚ค ์ œ์•ฝ ์กฐ๊ฑด์„ ์ œ๊ฑฐํ•œ ๋’ค ๊ทธ๋Œ€๋กœ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”?
์‚ฝ์ž…๋œ ๋ฐ์ดํ„ฐ(appointment_available_time_count) ๋Š” 100๊ฐœ์ธ๋ฐ, ์‹ค์ œ๋กœ ์—…๋ฐ์ดํŠธ๋œ ์„ ํƒ ์ธ์›(selected_count) ๋Š” 12๊ฐœ์ž…๋‹ˆ๋‹ค. ๋‘ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งž์ง€ ์•Š๋Š” ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.
์กฐํšŒ ์‹œ์— ์„œ๋กœ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ ๊ฐ™์€ count ๋ฅผ ์กฐํšŒํ•œ ๋’ค, ๊ฐ๊ฐ ํŠธ๋žœ์žญ์…˜์—์„œ update ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋’ค์— ์ˆ˜ํ–‰๋œ ํŠธ๋žœ์žญ์…˜์˜ update ๊ฐ€ ์•ž์˜ ํŠธ๋žœ์žญ์…˜์—์„œ update ํ–ˆ๋˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฎ์–ด ์”Œ์šฐ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ์•ž์„œ ์ˆ˜ํ–‰๋˜์—ˆ๋˜ ํŠธ๋žœ์žญ์…˜์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋ˆ„๋ฝ์ด ๋˜๊ณ  ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚™๊ด€๋ฝ

๋น„๊ด€์  ๋ฝ์€ ์•ž์„œ ์–ธ๊ธ‰ํ•œ ์ƒํ™ฉ๊ณผ ๊ฐ™์•„ ๋น„๊ด€์  ๋ฝ ๋Œ€์‹  ๋‚™๊ด€์  ๋ฝ์„ ์‚ฌ์šฉํ•ด ๋ณด๊ธฐ๋กœ ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋‚™๊ด€์  ๋ฝ์€ version ์ปฌ๋Ÿผ์ด ์กด์žฌํ•˜๊ณ , update ์‹œ ๊ธฐ์กด์— ์กฐํšŒํ–ˆ๋˜ version ๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ ํ›„ update ํ•ฉ๋‹ˆ๋‹ค. ๋ ˆ์ฝ”๋“œ์— ๋ฐฐํƒ€๋ฝ์„ ๊ฑธ์ง€ ์•Š๊ธฐ์— ์„ฑ๋Šฅ์ƒ ํฌ๊ฒŒ ์ €ํ•˜๋˜์ง€ ์•Š๊ณ  ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋ผ๊ณ  ํŒ๋‹จํ•˜์˜€์Šต๋‹ˆ๋‹ค.
@Query("select a from Appointment a where a.menu.code.code = :code") @Lock(LockModeType.OPTIMISTIC) Optional<Appointment> findByCode(@Param("code") String code);
Java
๋ณต์‚ฌ
JPA ์—์„œ๋Š” ๋น„๊ด€์  ๋ฝ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ @Lock ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ๋‚™๊ด€์  ๋ฝ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
alter table appointment add column version int default 1;
SQL
๋ณต์‚ฌ
๊ทธ๋ฆฌ๊ณ  ๋„๋ฉ”์ธ ๊ฐ์ฒด์— version ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€ ๋’ค ํ•ด๋‹น ํ•„๋“œ์— @Version ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๋ฉด ๋‚™๊ด€์  ๋ฝ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค๋ณด๋ฉด!
update ํ•  ๋•Œ ์ˆ˜์ •ํ•  ๋ ˆ์ฝ”๋“œ ์ด์™ธ์—๋„ version ์ด ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด version ๋„ ํ•จ๊ป˜ ์กฐ๊ฑด์ ˆ์— ์ถ”๊ฐ€ํ•˜์—ฌ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ…Œ์ŠคํŠธํ•œ 100๊ฐœ ๋ชจ๋‘ ์ ์šฉ๋˜์ง€ ์•Š์•„ ํ…Œ์ŠคํŠธ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ์ˆ˜์ •์— ์‹คํŒจํ•˜๋ฉด ๋กค๋ฐฑ์„ ์ง„ํ–‰ํ•˜์—ฌ ์•ž์„œ ์‚ฝ์ž…ํ•œ ๋ฐ์ดํ„ฐ๋„ ๋ชจ๋‘ ์ทจ์†Œ๋˜๊ณ  ๋กค๋ฐฑ๋ฉ๋‹ˆ๋‹ค. ๋‚™๊ด€์  ๋ฝ์„ ์ ์šฉํ•œ ๋กœ์ง์˜ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
์ˆ˜์ •ํ•œ ๊ฐ’๊ณผ ์‚ฝ์ž…๋œ ๊ฐ’์ด ์ผ์น˜ํ•˜์—ฌ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜๋Š” ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ๊ฒฐ๊ณผ์ ์œผ๋กœ ์œ ์‹ค๋œ ๋ฐ์ดํ„ฐ๊ฐ€ 87๊ฑด์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์œ ์‹ค์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ rollback ์ด ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๋‹ค์‹œ ๋™์ผํ•œ ๊ณผ์ •์„ ์ˆ˜ํ–‰ํ•˜๋Š” while ๋ฐ˜๋ณต๋ฌธ์„ ๋Œ๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ด๋ฏธ ๋„๋ฉ”์ธ ๊ฐ์ฒด์—๋Š” ๋‚™๊ด€์  ๋ฝ๋งŒ์„ ์œ„ํ•œ version ํ•„๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ •ํ•ฉ์„ฑ๊ณผ ๋ฐ์ดํ„ฐ ์œ ์‹ค์—†๋Š” ๋‚™๊ด€์  ๋ฝ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํฌ์ƒํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๋‚™๊ด€์  ๋ฝ์€ ๋ณด๋ฅ˜ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๋กœ์ง์œผ๋กœ ํ’€๊ธฐ

๋งˆ์ง€๋ง‰ ๋ฐฉ๋ฒ•์€ count ๊ฐ’์„ ์ˆ˜์ •ํ•˜๋Š” ๋”ํ‹ฐ ์ฒดํ‚น์„ ํฌ๊ธฐํ•˜๊ณ  ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ๋‚ ๋ฆฌ๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ๋Š” count ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€๋งŒ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ˆ˜์ •ํ•˜๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ๋ฉด JPA ์—์„œ๋Š” flush ๋ฅผ ํ•˜๊ณ  ๋”ํ‹ฐ ์ฒดํ‚น์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜๋Š” ์ฟผ๋ฆฌ๋งŒ ์ƒ์„ฑํ•œ ๋’ค, ์ˆ˜์ •ํ•˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ˆ˜์ •ํ•˜๋Š” ์ฟผ๋ฆฌ๋Š” ์ˆ˜์ •ํ•˜๋Š” ์‹œ์ ์˜ ๊ฐ’์—์„œ +1 ํ•˜๋Š” ๋กœ์ง์ด๊ธฐ๋•Œ๋ฌธ์— ์ •ํ•ฉ์„ฑ์„ ๋งž์ถœ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์•ž์„œ ์–ธ๊ธ‰๋“œ๋ฆฐ๋Œ€๋กœ update ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž์ฒด์ ์œผ๋กœ ๋ฐฐํƒ€๋ฝ์„ ํš๋“ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๋‹ค๋ฅธ ๋กœ์ง์œผ๋กœ ์ธํ•œ ๊ณต์œ ๋ฝ์„ ํš๋“ํ•˜์ง€ ์•Š๊ธฐ์— ๋ฐ๋“œ๋ฝ ๋ฌธ์ œ๋„ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
@Modifying(clearAutomatically =true, flushAutomatically =true) @Query("update Appointment a " + "set a.availableTimes.selectedCount = a.availableTimes.selectedCount + 1 " + "where a.menu.code.code = :appointmentCode") void updateSelectedCount(@Param("appointmentCode") String appointmentCode);
Java
๋ณต์‚ฌ
ํ˜„์žฌ ์ €์žฅ๋œ ํšŒ์›์˜ ํŒ”๋กœ์›Œ ์นด์šดํŠธ ๊ฐ’์— +1์„ ํ•ด์ฃผ๋Š” JPQL์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค ๋ณด๋ฉด!
์ •ํ•ฉ์„ฑ ๋ฌธ์ œ์™€ ๋ฐ์ดํ„ฐ ์œ ์‹ค ๋ฌธ์ œ ๋ชจ๋‘ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
์œ„์˜ ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•๋“ค๊ณผ ๋น„๊ตํ•ด๋ณด๋ฉด, JPA์˜ ๋”ํ‹ฐ ์ฒดํ‚น ๊ธฐ๋Šฅ์„ ํฌ๊ธฐํ•˜์—ฌ ๋œ ๊ฐ์ฒด ์ง€ํ–ฅ์ ์ธ ์ฝ”๋“œ๊ฐ€ ๋˜๊ณ , ๋„๋ฉ”์ธ์˜ ๋กœ์ง์ด ์„œ๋น„์Šค ๊ณ„์ธต์œผ๋กœ ์ด๋™ํ•ด ๋น„๋Œ€ํ•œ ์„œ๋น„์Šค ์ฝ”๋“œ๋ฅผ ๋งŒ๋“œ๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ JPQL ์„ ์ ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ๊ฐ์ฒด๋ฅผ flush ํ•˜์—ฌ ์ดํ›„ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ์ด์šฉํ•˜๋ ค๋ฉด ๋‹ค์‹œ ์กฐํšŒ๋ฅผ ํ•ด์•ผ ํ•˜๋Š” ๋“ฑ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ๋น„ํšจ์œจ์ ์œผ๋กœ ์ด์šฉํ•  ์ˆ˜ ๋ฐ–์— ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฝ์„ ์ตœ์†Œํ™”ํ•˜๋ฉด์„œ ์ •ํ•ฉ์„ฑ์„ ํ™•์‹คํ•˜๊ฒŒ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๊ณ  ๋ฐ์ดํ„ฐ ์œ ์‹ค ๋ฌธ์ œ๋„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๊ธฐ๋„ ํ•˜๊ณ , ํ•ด๋‹น ๋กœ์ง ์ดํ›„์—๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ์ด์šฉํ•˜๋Š” ๋กœ์ง์ด ์—†๊ธฐ์— ์ €ํฌ๋Š” ๋กœ์ง์œผ๋กœ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.