- Tạo ứng dụng Spring Boot bằng Spring Launchizr
- Cấu hình Cassandra trong Spring Boot
- Định cấu hình nhóm kết nối Tomcat trên Spring Boot
- Định tuyến tin nhắn Camel đến Artemis được nhúng bằng WildFly
Khi không có sự cạnh tranh đa luồng, các đường dẫn thực thi khóa nhẹ không cần thiết sẽ bị giảm. Trong hầu hết các trường hợp, không những không có sự cạnh tranh đa luồng đối với các khóa mà còn cùng một luồng luôn lấy được khóa nhiều lần. Để làm cho chi phí hiệu năng của các luồng có được khóa thấp hơn, các khóa thiên vị được đưa ra.
Khóa thiên vị chủ yếu được sử dụng để tối ưu hóa cạnh tranh khi cùng một luồng áp dụng cho cùng một khóa nhiều lần. Nghĩa là, khi đối tượng được coi là khóa đồng bộ hóa và một luồng lấy khóa, hãy đặt ID luồng của luồng trong Mark Word và. có đặt khóa thiên vị hay không 1. Bit cờ khóa được đặt thành 01 và các thông tin khác. Tại thời điểm này, Mark Word lưu trữ thông tin trạng thái khóa thiên vị.
hiện hữu:
Cùng một luồng cần lấy và giải phóng khóa mỗi lần và mỗi thao tác sẽ chuyển đổi giữa chế độ người dùng và chế độ kernel.
Các kịch bản để có được khóa thiên vị:
Tạo Bản ghi khóa trong ngăn xếp luồng của riêng bạn và sau đó Tham chiếu đối tượng trỏ đến tiêu đề đối tượng. Tại thời điểm này, Bản ghi khóa và tiêu đề đối tượng được kết nối:
①: Đầu tiên hãy xác định xem Thread ID của Mard Word có giá trị hay không
Nếu không, điều đó có nghĩa là tài nguyên hiện tại không bị chiếm bởi các luồng khác. Ghi lại ID luồng hiện tại và thông tin khác vào Mark Word (điều này yêu cầu CAS, nhiều luồng có thể sửa đổi Mark Word và cần đảm bảo tính nguyên tử)
Nếu có, điều đó có nghĩa là tài nguyên hiện tại đã bị chiếm bởi luồng và bạn cần xác định xem luồng đó có phải là của riêng bạn không
Nếu ID luồng là của riêng bạn, điều đó có nghĩa là nó có thể được đăng nhập lại và lấy trực tiếp (tại thời điểm này, Bản ghi khóa mới sẽ tiếp tục được tạo trong ngăn xếp luồng của riêng bạn)
ID luồng không phải của riêng bạn, cho biết rằng các luồng khác cạnh tranh và luồng hiện đang giữ khóa thiên vị cần được thu hồi, nghĩa là khóa chỉ được giải phóng khi các luồng khác cố gắng lấy khóa thiên vị.
Việc thu thập và giải phóng các khóa hạng nhẹ phụ thuộc vào nhiều hoạt động CAS, trong khi các khóa thiên vị chỉ dựa vào một lần thay thế CAS.ID chủ đề
.
Một khi nó xuất hiệnNhiều chủ đề cạnh tranhKhóa thiên vị phải được thu hồi, vì vậy:
Mức tiêu thụ hiệu suất của việc hủy khóa thiên vị phải < mức tiêu thụ hiệu suất của hoạt động nguyên tử CAS đã lưu trước đó
Ngược lại, cái được nhiều hơn cái mất!
JDK6 cho phép khóa thiên vị theo mặc định, có thể được thông qua-XX:-Sử dụngBiasedLocking
Vô hiệu hóa khóa thiên vị.
Lối vào khóa thiên vị, trong tệp syncer.cpp
phụ thuộc vàoBiasedLocking::revoke_and_rebias
hoàn thành
Lấy dấu dữ liệu markOop của đối tượng, tức là Mark Word của tiêu đề đối tượng
Cờ khóa của khóa thiên vị của nhãn hiệu là 01
Đặt dấu JavaThread thành ID luồng hiện tại.
Nếu CAS thành công, thực thi khối mã đồng bộ, nếu không thì chuyển sang bước 5.
Điều đó có nghĩa là hiện có nhiều luồng cạnh tranh để giành khóa. Khi đạt đến điểm an toàn toàn cầu (điểm an toàn), luồng nhận được khóa thiên vị sẽ bị tạm dừng, khóa thiên vị sẽ bị thu hồi và nó sẽ được nâng cấp lên mức nhẹ. khóa.
Sau khi nâng cấp hoàn tất, luồng bị chặn ở điểm an toàn tiếp tục thực thi khối mã được đồng bộ hóa.
BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool try_rebias, TRAPS) { khẳng định(!SafepointSynchronize::is_at_safepoint(), "không được gọi khi đang ở điểm an toàn" // Chúng ta có thể hủy bỏ các thành kiến của thành kiến ẩn danh"); đối tượng // đủ hiệu quả để chúng ta không khiến các lệnh thu hồi này // cập nhật các phương pháp phỏng đoán vì làm như vậy có thể gây ra việc thu hồi số lượng lớn // không mong muốn (tốn kém) // step1 markOop mark = obj->mark(); if (mark->is_biased_anonymous() && !attempt_rebias) { // Có lẽ chúng tôi đang cố gắng thu hồi. độ lệch của đối tượng này do // tính toán mã băm nhận dạng. Cố gắng hủy bỏ độ lệch // mà không có điểm an toàn. Điều này có thể thực hiện được nếu chúng ta có thể so sánh và trao đổi thành công một tiêu đề không thiên vị vào đối tượng. đánh dấu từ của // đối tượng, nghĩa là không có luồng nào khác chạy đua để đạt được // độ lệch của đối tượng. markOopbiased_value = mark; ; markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark ==biased_value) { return BIAS_REVOKED; } } else if (mark->has_bias_pattern()) { Klass* k = obj->klass(); >prototype_header(); if (!prototype_header->has_bias_pattern()) { // Đối tượng này có xu hướng cũ từ trước khi xảy ra việc thu hồi hàng loạt // đối với loại dữ liệu này. Việc cập nhật // chẩn đoán vào thời điểm này là vô nghĩa, vì vậy chỉ cần cập nhật tiêu đề bằng // CAS. Nếu chúng tôi thất bại trong cuộc đua này, xu hướng của đối tượng. đã bị thu hồi // bởi một luồng khác nên chúng tôi chỉ cần quay lại và để người gọi xử lý // với nó. Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark); khẳng định(!(*(obj->mark_addr()))->has_bias_pattern(), "ngay cả khi chúng tôi đua, vẫn nên bị thu hồi"); trả về BIAS_REVOKED } khác nếu (prototype_header->bias_epoch() != mark->bias_epoch()) { // Kỷ nguyên của xu hướng này đã hết cho thấy rằng // đối tượng thực sự không thiên vị. Tùy thuộc vào việc chúng ta cần // phục hồi hay thu hồi độ lệch của đối tượng này, chúng ta có thể thực hiện // một cách hiệu quả. với CAS là đủ nên chúng ta không nên cập nhật // chẩn đoán. Điều này thường được thực hiện trong mã hợp ngữ nhưng chúng ta // có thể đạt đến điểm này do có nhiều điểm khác nhau trong thời gian chạy // cần phải thu hồi các thành kiến nếu. (attempt_rebias) { khẳng định(THREAD->is_Java_thread(), ""); markOopbiased_value = mark; markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), Prototype_header->bias_epoch()) ; markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark); if (res_mark ==biased_value) { return BIAS_REVOKED_AND_REBIASED; } } else { markOopbiased_value = mark; markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype,obj->mark_addr(), mark); if (res_mark ==biased_value) { return BIAS_REVOKED; } } } } HeuristicsResult heuristics = update_heuristics(obj(), try_rebias); if (heuristics == HR_NOT_BIASED) { return NOT_BIASED; nếu (heuristic == HR_SINGLE_REVOKE) { Klass *k = obj->klass(); markOop nguyên mẫu_header = k->prototype_header(); if (mark->biased_locker() == THREAD && nguyên mẫu_header->bias_epoch() == mark->bias_epoch() ) { // Một luồng đang cố gắng thu hồi sự thiên vị của một đối tượng thiên về // đối với nó, một lần nữa có thể là do một mã băm nhận dạng // tính toán. Một lần nữa, chúng ta có thể tránh điểm an toàn trong trường hợp này // vì chúng ta sẽ chỉ đi theo ngăn xếp của riêng mình. Không có // cuộc chạy đua nào xảy ra trong các luồng khác vì chúng ta // không đạt được điểm an toàn nào trong đó. đường dẫn thu hồi. // Đồng thời kiểm tra kỷ nguyên vì ngay cả khi các luồng khớp nhau, một luồng khác // có thể đi kèm với CAS để đánh cắp độ lệch của một đối tượng có // kỷ nguyên cũ. ResourceMark rm; tty->print_cr("Thu hồi độ lệch bằng cách di chuyển ngăn xếp của riêng tôi:"); } EventBiasedLockSelfRevocation; ) THREAD)->set_cached_monitor_info(NULL); khẳng định(cond == BIAS_REVOKED, "tại sao không?"); if (event. Should_commit()) { event.set_lockClass(k); event.commit(); } return cond; } else { Sự kiện EventBiasedLockRevocation; THREAD); VMThread::execute(&revoke); (event. Should_commit() && (revoke.status_code() != NOT_BIASED)) { event.set_lockClass(k); // Trừ 1 để khớp với id của các sự kiện đã cam kết bên trong safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); sự kiện.set_previousOwner(revoke.biased_locker()); event.commit(); } return revoke.status_code(); } } khẳng định((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); Sự kiện EventBiasedLockClassRevocation; CHỦ ĐỀ, (heuristic == HR_BULK_REBIAS), try_rebias); VMThread::execute(&bulk_revoke); if (event. Should_commit()) { event.set_revokedClass(obj->klass()); event.set_disableBiasing((heuristics != HR_BULK_REBIAS)); 1 để khớp với id của các sự kiện đã cam kết bên trong điểm an toàn event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.commit() } return Bulk_revoke.status_code();một lần nữa có thể là do tính toán mã băm nhận dạng //. Chúng ta lại có thể tránh điểm an toàn trong trường hợp này // vì chúng ta sẽ chỉ đi theo ngăn xếp của chính mình. Không có // cuộc đua nào xảy ra trong các luồng khác vì chúng ta // không đạt đến điểm an toàn trong đường dẫn thu hồi. // Ngoài ra, hãy kiểm tra kỷ nguyên vì ngay cả khi các luồng khớp nhau, một luồng khác // có thể đi kèm với CAS để đánh cắp độ lệch của một đối tượng có // kỷ nguyên cũ if. (TraceBiasedLocking) { tty->print_cr("Thu hồi độ lệch bằng cách di chuyển ngăn xếp của riêng tôi:"); } EventBiasedLockSelfRevocation event; BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD); ((JavaThread*) THREAD)->set_cached_monitor_info(NULL); khẳng định (cond == BIAS_REVOKED, "tại sao không?"); if (event. Should_commit()) { event.set_lockClass(k); event.commit(); return cond; } else { EventBiasedLockRevocation; , (JavaThread*) THREAD); VMThread::execute(&revoke); (event. Should_commit() && (revoke.status_code() != NOT_BIASED)) { event.set_lockClass(k); // Trừ 1 để khớp với id của các sự kiện đã cam kết bên trong safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); sự kiện.set_previousOwner(revoke.biased_locker()); event.commit(); } return revoke.status_code(); } } khẳng định((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); Sự kiện EventBiasedLockClassRevocation; CHỦ ĐỀ, (heuristic == HR_BULK_REBIAS), try_rebias); VMThread::execute(&bulk_revoke); if (event. Should_commit()) { event.set_revokedClass(obj->klass()); event.set_disableBiasing((heuristics != HR_BULK_REBIAS)); 1 để khớp với id của các sự kiện đã cam kết bên trong điểm an toàn event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.commit() } return Bulk_revoke.status_code();một lần nữa có thể là do tính toán mã băm nhận dạng //. Chúng ta lại có thể tránh điểm an toàn trong trường hợp này // vì chúng ta sẽ chỉ đi theo ngăn xếp của chính mình. Không có // cuộc đua nào xảy ra trong các luồng khác vì chúng ta // không đạt được điểm an toàn nào trong đường dẫn thu hồi. // Ngoài ra, hãy kiểm tra kỷ nguyên vì ngay cả khi các luồng khớp nhau, một luồng khác // có thể đi kèm với CAS để đánh cắp độ lệch của một đối tượng có // kỷ nguyên cũ if. (TraceBiasedLocking) { tty->print_cr("Thu hồi độ lệch bằng cách di chuyển ngăn xếp của riêng tôi:"); } EventBiasedLockSelfRevocation sự kiện; ((JavaThread*) THREAD)->set_cached_monitor_info(NULL); khẳng định (cond == BIAS_REVOKED, "tại sao không?"); if (event. Should_commit()) { event.set_lockClass(k); event.commit(); } return cond; } else { EventBiasedLockRevocation; , (JavaThread*) THREAD); VMThread::execute(&revoke); (event. Should_commit() && (revoke.status_code() != NOT_BIASED)) { event.set_lockClass(k); // Trừ 1 để khớp với id của các sự kiện đã cam kết bên trong safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); sự kiện.set_previousOwner(revoke.biased_locker()); event.commit(); } return revoke.status_code(); } } khẳng định((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); Sự kiện EventBiasedLockClassRevocation; CHỦ ĐỀ, (heuristic == HR_BULK_REBIAS), try_rebias); VMThread::execute(&bulk_revoke); if (event. Should_commit()) { event.set_revokedClass(obj->klass()); event.set_disableBiasing((heuristics != HR_BULK_REBIAS)); 1 để khớp với id của các sự kiện đã cam kết bên trong điểm an toàn event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.commit() } return Bulk_revoke.status_code();set_lockClass(k); // Trừ 1 để khớp với id của các sự kiện đã được cam kết bên trong safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); } return revoke.status_code(); } } khẳng định((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); Sự kiện EventBiasedLockClassRevocation; VMThread::execute(&bulk_revoke); if (event. Should_commit()) { event.set_revokedClass(obj->klass()); event.set_disableBiasing((heuristics != HR_BULK_REBIAS)); // Trừ 1 để khớp với id của các sự kiện được cam kết bên trong điểm an toàn event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.commit() } return Bulk_revoke.status_code();set_lockClass(k); // Trừ 1 để khớp với id của các sự kiện đã được cam kết bên trong safepoint event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); } return revoke.status_code(); } } khẳng định((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); Sự kiện EventBiasedLockClassRevocation; VMThread::execute(&bulk_revoke); if (event. Should_commit()) { event.set_revokedClass(obj->klass()); event.set_disableBiasing((heuristics != HR_BULK_REBIAS)); // Trừ 1 để khớp với id của các sự kiện được cam kết bên trong điểm an toàn event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1); event.commit() } return Bulk_revoke.status_code();
Luồng giữ khóa thiên vị sẽ chỉ giải phóng khóa khi các luồng khác cố gắng cạnh tranh khóa thiên vị.
Khóa thiên vị bị hủy bởiBiasedLocking::revoke_at_safepoint
hoàn thành:
void BiasedLocking::revoke_at_safepoint(Handle h_obj) { khẳng định(SafepointSynchronize::is_at_safepoint(), "chỉ được gọi tại điểm an toàn"); oop obj = h_obj(); = HR_SINGLE_REVOKE) { revoke_bias(obj, false, false, NULL, NULL); } else if ((heuristics == HR_BULK_REBIAS) || (heuristics == HR_BULK_REVOKE)) { Bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL } clean_up_cached_monitor_info(); }
Khóa thiên vị được bật theo mặc định sau Java 1.6, nhưng nó không được kích hoạt cho đến vài giây sau khi ứng dụng khởi động:
-XX:BiasedLockingStartupDelay=0
Nếu bạn xác định rằng tất cả các khóa trong ứng dụng thường bị tranh chấp, bạn có thể tắt các khóa thiên vị:
XX:-UseBiasedLocking=false (bật theo mặc định)
Duyệt qua tất cả các Bản ghi khóa trong ngăn xếp luồng và cắt bỏ ObjectReference, nghĩa là ObjectReference = null.
Đặt ObjectReference thành null, nhưng Mark Word trong tiêu đề đối tượng của đối tượng khóa không thay đổi và nó vẫn thiên về luồng trước đó, do đó khóa vẫn không được giải phóng. Thật vậy, khi luồng thoát khỏi phần quan trọng, khóa. khóa thiên vị không được giải phóng. Điều này là dành cho: Khi bạn cần lấy lại khóa, bạn chỉ cần đánh giá xem đó có phải là khóa lại hay không và bạn có thể nhanh chóng lấy được khóa mà không cần phải CAS mỗi lần. hiệu quả của khóa thiên vị trong trường hợp chỉ có một luồng truy cập vào khóa.
1. Khóa công bằng và khóa không công bằng 1.1. Tổng quan về khóa công bằng và khóa không công bằng Khóa công bằng: đề cập đến nhiều luồng lấy khóa theo thứ tự áp dụng cho khóa. Khóa không công bằng: Điều đó có nghĩa là thứ tự mà nhiều luồng nhận được khóa không theo thứ tự chúng áp dụng cho các khóa. Có thể luồng được áp dụng sau sẽ nhận được khóa trước luồng được áp dụng trước.
Đọc thư mục 1, giới thiệu 2, phân loại 3, khóa chung 4, khóa cấp bảng 5, khóa bảng 6, khóa siêu dữ liệu
Vì vậy, trong chương trình tôi viết, tôi có ba hàm, hãy gọi chúng là A, B và C cho đơn giản. Mọi chức năng đều yêu cầu quyền truy cập vào tài nguyên X để hoạt động. Hạn chế là A và B không được phép chạy cùng lúc và phải được đồng bộ hóa đúng cách. Tuy nhiên C có thể chạy đồng thời với A hoặc B
Tôi đã nghe những từ này liên quan đến lập trình đồng thời, nhưng sự khác biệt giữa khóa, mutexes và semaphores là gì? Câu trả lời hay nhất Khóa chỉ cho phép một luồng đi vào phần bị khóa và khóa không được chia sẻ với bất kỳ quy trình nào khác. Một mutex giống như một khóa, nhưng nó có thể được sử dụng trên toàn hệ thống (được chia sẻ bởi nhiều tiến trình).
Câu hỏi này đã có câu trả lời: Cách hiệu quả để triển khai mẫu đơn trong Java là gì?
Câu hỏi này đã có câu trả lời: Cách hiệu quả để triển khai mẫu đơn trong Java là gì?
Tôi có một số câu hỏi về chủ đề trong tiêu đề. Đầu tiên, giả sử chúng ta đang sử dụng JDBC và có 2 giao dịch T1 và T2. Trong T1, chúng tôi thực thi câu lệnh chọn trên một hàng cụ thể. Sau đó, chúng tôi thực hiện cập nhật trên hàng đó. Trong giao dịch T2, chúng ta
Tôi muốn chức năng của tôi chỉ chạy một lần. Điều này có nghĩa là nếu nhiều thread gọi nó cùng lúc thì hàm sẽ chặn tất cả các thread và chỉ cho phép nó chạy. Câu trả lời hay nhất Có vẻ như bạn muốn đồng bộ hóa thủ tục đã lưu trữ. Tại sao không chỉ đặt đồng bộ hóa trong chính ứng dụng. pthread_mute
if (runInDemoMode) { lock (this) { //Khởi tạo bảng dCreator.create
Tôi chắc chắn rằng vấn đề của tôi có thể được xem xét bất kể ngôn ngữ nào, nhưng để có một số "mỏ neo" nào đó, tôi sẽ sử dụng ngôn ngữ Java để mô tả nó. Hãy xem xét tình huống sau: Tôi có một lớp mở rộng Thread PickyHost và phiên bản của nó p
Tôi biết rằng async không song song, nhưng hiện tại tôi có một tình huống rất thú vị. hàm async magic(){ /* một số xử lý ở đây */ đang chờ async () =
Chúng tôi đang xây dựng một ứng dụng web bằng Scala, Play framework và MongoDB (với ReactiveMongo làm trình điều khiển của chúng tôi). Kiến trúc ứng dụng là không chặn từ đầu đến cuối. Trong một số phần của mã, chúng tôi cần truy cập
Tôi cần một khóa đơn giản với thời gian chờ JavaME (backport của concurrent.lock yêu cầu Java 1.3 đầy đủ). Nếu ai đó đã phát hành mã khóa đã được thử nghiệm cho JavaME thì tôi muốn sử dụng nó hơn. Lệnh khóa đã hết
Theo boost: Để truy cập đối tượng, một yếu_ptr có thể được chuyển đổi thành một Shared_ptr bằng cách sử dụng co chia sẻ_ptr
Có một câu hỏi về sự khác biệt giữa phần Mutex và phần Quan trọng, nhưng nó cũng không xử lý được Khóa. Vì vậy, tôi muốn biết liệu các phần quan trọng có thể được sử dụng để đồng bộ hóa luồng giữa các quy trình hay không. Ngoài ra còn có ý nghĩa của trạng thái có tín hiệu và trạng thái không có tín hiệu. Câu trả lời hay nhất là ở Win.
Ứng dụng phổ biến nhất của khóa là kiểm soát hàng tồn kho trong điều kiện đồng thời cao. Lần này tôi sẽ chỉ giới thiệu đơn giản về các ổ khóa độc lập. Nhìn thẳng vào mã: Inventory -1 cho mỗi request. Nếu Inventory là 1.000 thì sau 1.000 request, Inventory sẽ trở thành 0.
Các luồng và tiến trình 1. Các luồng chia sẻ không gian địa chỉ của tiến trình đã tạo ra nó và tiến trình có không gian địa chỉ riêng 2. Các luồng có thể truy cập tất cả dữ liệu của tiến trình và các luồng có thể truy cập lẫn nhau 3. Dữ liệu giữa các luồng là độc lập 4 . Sao chép tiến trình con Dữ liệu luồng 5. Khởi động tiến trình con
**Tóm tắt:** Nếu cẩn thận, bạn hẳn đã nhận thấy rằng một số URL bắt đầu bằng https và một số là http. Phía trước trang web sẽ có một chiếc khóa nhỏ bắt đầu bằng https. Tại sao lại thế này? Bài viết này được chia sẻ từ Cộng đồng Huawei Cloud "Bạn không biết rằng chứng chỉ SSL chỉ cần thiết sao?" Hãy đến và tìm hiểu nhanh chóng
Tôi hơi bối rối khi cố gắng triển khai một mutex (khóa) rất đơn giản trong C. Tôi biết rằng một mutex tương tự như một semaphore nhị phân, ngoại trừ một mutex cũng thực thi ràng buộc rằng luồng giải phóng khóa phải là cùng một luồng mà gần đây nhất đã có được nó. Tôi đang bối rối về cách theo dõi quyền sở hữu? Đây là những gì tôi đã làm cho đến nay
Sau khi đọc rất nhiều bài viết và câu trả lời liên quan đến chủ đề trên, tôi vẫn muốn biết công cụ cơ sở dữ liệu SQL Server hoạt động như thế nào trong ví dụ sau: Giả sử chúng ta có một bảng có tên t3: tạo bảng t3 (a int ,
Tôi là một lập trình viên xuất sắc, rất giỏi!