diff --git a/changelogs/unreleased/207312-remove-optimisticlocking-monkeypatch.yml b/changelogs/unreleased/207312-remove-optimisticlocking-monkeypatch.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e2084b8170f51f1d2307509a89d113436a1154b2
--- /dev/null
+++ b/changelogs/unreleased/207312-remove-optimisticlocking-monkeypatch.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Rails Optimistic Locking monkeypatch
+merge_request: 25566
+author:
+type: fixed
diff --git a/config/initializers/config_initializers_active_record_locking.rb b/config/initializers/config_initializers_active_record_locking.rb
deleted file mode 100644
index 9f9908283c635b6a47b8c923ba30246e40e846c2..0000000000000000000000000000000000000000
--- a/config/initializers/config_initializers_active_record_locking.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-# ensure ActiveRecord's version has been required already
-require 'active_record/locking/optimistic'
-
-# rubocop:disable Lint/RescueException
-module ActiveRecord
-  module Locking
-    module Optimistic
-      private
-
-      def _update_row(attribute_names, attempted_action = "update")
-        return super unless locking_enabled?
-
-        begin
-          locking_column = self.class.locking_column
-          previous_lock_value = read_attribute_before_type_cast(locking_column)
-          attribute_names << locking_column
-
-          self[locking_column] += 1
-
-          # Patched because when `lock_version` is read as `0`, it may actually be `NULL` in the DB.
-          possible_previous_lock_value = previous_lock_value.to_i == 0 ? [nil, 0] : previous_lock_value
-
-          affected_rows = self.class.unscoped.where(
-            locking_column => possible_previous_lock_value,
-            self.class.primary_key => id_in_database
-          ).update_all(
-            attributes_with_values(attribute_names)
-          )
-
-          if affected_rows != 1
-            raise ActiveRecord::StaleObjectError.new(self, attempted_action)
-          end
-
-          affected_rows
-
-        # If something went wrong, revert the locking_column value.
-        rescue Exception
-          self[locking_column] = previous_lock_value.to_i
-          raise
-        end
-      end
-    end
-  end
-end
diff --git a/db/post_migrate/20200608195222_set_lock_version_not_null_constraint.rb b/db/post_migrate/20200608195222_set_lock_version_not_null_constraint.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec72053b3078a1dcc6da6b29500e592be1504883
--- /dev/null
+++ b/db/post_migrate/20200608195222_set_lock_version_not_null_constraint.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class SetLockVersionNotNullConstraint < ActiveRecord::Migration[6.0]
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  TABLES = %i(epics merge_requests issues ci_stages ci_builds ci_pipelines).freeze
+
+  def up
+    TABLES.each do |table|
+      add_not_null_constraint table, :lock_version, validate: false
+    end
+  end
+
+  def down
+    TABLES.each do |table|
+      remove_not_null_constraint table, :lock_version
+    end
+  end
+end
diff --git a/db/post_migrate/20200608205813_set_lock_version_to_not_null.rb b/db/post_migrate/20200608205813_set_lock_version_to_not_null.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fc97484ff4d652822f543a90529b18bd4ac24f48
--- /dev/null
+++ b/db/post_migrate/20200608205813_set_lock_version_to_not_null.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class SetLockVersionToNotNull < ActiveRecord::Migration[6.0]
+  DOWNTIME = false
+
+  MODELS = [Epic, MergeRequest, Issue, Ci::Stage, Ci::Build, Ci::Pipeline].freeze
+
+  disable_ddl_transaction!
+
+  def up
+    MODELS.each do |model|
+      model.where(lock_version: nil).update_all(lock_version: 0)
+    end
+  end
+
+  def down
+    # Nothing to do...
+  end
+end
diff --git a/db/post_migrate/20200608212030_lock_version_cleanup_for_epics.rb b/db/post_migrate/20200608212030_lock_version_cleanup_for_epics.rb
new file mode 100644
index 0000000000000000000000000000000000000000..70faa1caca0b3b153c2c07893981f203763d32e7
--- /dev/null
+++ b/db/post_migrate/20200608212030_lock_version_cleanup_for_epics.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForEpics < ActiveRecord::Migration[6.0]
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    validate_not_null_constraint :epics, :lock_version
+    remove_concurrent_index :epics, :lock_version, where: "lock_version IS NULL"
+  end
+
+  def down
+    add_concurrent_index :epics, :lock_version, where: "lock_version IS NULL"
+  end
+end
diff --git a/db/post_migrate/20200608212435_lock_version_cleanup_for_merge_requests.rb b/db/post_migrate/20200608212435_lock_version_cleanup_for_merge_requests.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bc4ac3bdc3189ac2d460f40c79b3966874c40ca4
--- /dev/null
+++ b/db/post_migrate/20200608212435_lock_version_cleanup_for_merge_requests.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForMergeRequests < ActiveRecord::Migration[6.0]
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    validate_not_null_constraint :merge_requests, :lock_version
+    remove_concurrent_index :merge_requests, :lock_version, where: "lock_version IS NULL"
+  end
+
+  def down
+    add_concurrent_index :merge_requests, :lock_version, where: "lock_version IS NULL"
+  end
+end
diff --git a/db/post_migrate/20200608212549_lock_version_cleanup_for_issues.rb b/db/post_migrate/20200608212549_lock_version_cleanup_for_issues.rb
new file mode 100644
index 0000000000000000000000000000000000000000..21936ac4fa6885608ebcde7945069b98b83ab4ae
--- /dev/null
+++ b/db/post_migrate/20200608212549_lock_version_cleanup_for_issues.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForIssues < ActiveRecord::Migration[6.0]
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    validate_not_null_constraint :issues, :lock_version
+    remove_concurrent_index :issues, :lock_version, where: "lock_version IS NULL"
+  end
+
+  def down
+    add_concurrent_index :issues, :lock_version, where: "lock_version IS NULL"
+  end
+end
diff --git a/db/post_migrate/20200608212652_lock_version_cleanup_for_ci_stages.rb b/db/post_migrate/20200608212652_lock_version_cleanup_for_ci_stages.rb
new file mode 100644
index 0000000000000000000000000000000000000000..12e2897123e30ab2fc37f8276c2c943a60ee9dd8
--- /dev/null
+++ b/db/post_migrate/20200608212652_lock_version_cleanup_for_ci_stages.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForCiStages < ActiveRecord::Migration[6.0]
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    validate_not_null_constraint :ci_stages, :lock_version
+    remove_concurrent_index :ci_stages, :id, where: "lock_version IS NULL", name: "tmp_index_ci_stages_lock_version"
+  end
+
+  def down
+    add_concurrent_index :ci_stages, :id, where: "lock_version IS NULL", name: "tmp_index_ci_stages_lock_version"
+  end
+end
diff --git a/db/post_migrate/20200608212807_lock_version_cleanup_for_ci_builds.rb b/db/post_migrate/20200608212807_lock_version_cleanup_for_ci_builds.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0512869971bfbd6f10e1b9c30f018247bf1d0aab
--- /dev/null
+++ b/db/post_migrate/20200608212807_lock_version_cleanup_for_ci_builds.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForCiBuilds < ActiveRecord::Migration[6.0]
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    validate_not_null_constraint :ci_builds, :lock_version
+    remove_concurrent_index :ci_builds, :id, where: "lock_version IS NULL", name: "tmp_index_ci_builds_lock_version"
+  end
+
+  def down
+    add_concurrent_index :ci_builds, :id, where: "lock_version IS NULL", name: "tmp_index_ci_builds_lock_version"
+  end
+end
diff --git a/db/post_migrate/20200608212824_lock_version_cleanup_for_ci_pipelines.rb b/db/post_migrate/20200608212824_lock_version_cleanup_for_ci_pipelines.rb
new file mode 100644
index 0000000000000000000000000000000000000000..228dd72da8de551c1c02e426587890aa960b8d77
--- /dev/null
+++ b/db/post_migrate/20200608212824_lock_version_cleanup_for_ci_pipelines.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class LockVersionCleanupForCiPipelines < ActiveRecord::Migration[6.0]
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    validate_not_null_constraint :ci_pipelines, :lock_version
+    remove_concurrent_index :ci_pipelines, :id, where: "lock_version IS NULL", name: "tmp_index_ci_pipelines_lock_version"
+  end
+
+  def down
+    add_concurrent_index :ci_pipelines, :id, where: "lock_version IS NULL", name: "tmp_index_ci_pipelines_lock_version"
+  end
+end
diff --git a/db/structure.sql b/db/structure.sql
index d6fce33622dd8568fecd2f640f30804594874980..73957ce9a69fc0723aebf8f3dbab771c2c3f3eb0 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1052,7 +1052,8 @@ CREATE TABLE public.ci_builds (
     resource_group_id bigint,
     waiting_for_resource_at timestamp with time zone,
     processed boolean,
-    scheduling_type smallint
+    scheduling_type smallint,
+    CONSTRAINT check_1e2fbd1b39 CHECK ((lock_version IS NOT NULL))
 );
 
 CREATE SEQUENCE public.ci_builds_id_seq
@@ -1362,7 +1363,8 @@ CREATE TABLE public.ci_pipelines (
     source_sha bytea,
     target_sha bytea,
     external_pull_request_id bigint,
-    ci_ref_id bigint
+    ci_ref_id bigint,
+    CONSTRAINT check_d7e99a025e CHECK ((lock_version IS NOT NULL))
 );
 
 CREATE TABLE public.ci_pipelines_config (
@@ -1547,7 +1549,8 @@ CREATE TABLE public.ci_stages (
     name character varying,
     status integer,
     lock_version integer DEFAULT 0,
-    "position" integer
+    "position" integer,
+    CONSTRAINT check_81b431e49b CHECK ((lock_version IS NOT NULL))
 );
 
 CREATE SEQUENCE public.ci_stages_id_seq
@@ -2506,7 +2509,8 @@ CREATE TABLE public.epics (
     start_date_sourcing_epic_id integer,
     due_date_sourcing_epic_id integer,
     confidential boolean DEFAULT false NOT NULL,
-    external_key character varying(255)
+    external_key character varying(255),
+    CONSTRAINT check_fcfb4a93ff CHECK ((lock_version IS NOT NULL))
 );
 
 CREATE SEQUENCE public.epics_id_seq
@@ -3532,7 +3536,8 @@ CREATE TABLE public.issues (
     promoted_to_epic_id integer,
     health_status smallint,
     external_key character varying(255),
-    sprint_id bigint
+    sprint_id bigint,
+    CONSTRAINT check_fba63f706d CHECK ((lock_version IS NOT NULL))
 );
 
 CREATE SEQUENCE public.issues_id_seq
@@ -4111,7 +4116,8 @@ CREATE TABLE public.merge_requests (
     state_id smallint DEFAULT 1 NOT NULL,
     rebase_jid character varying,
     squash_commit_sha bytea,
-    sprint_id bigint
+    sprint_id bigint,
+    CONSTRAINT check_970d272570 CHECK ((lock_version IS NOT NULL))
 );
 
 CREATE TABLE public.merge_requests_closing_issues (
@@ -9835,8 +9841,6 @@ CREATE INDEX index_epics_on_iid ON public.epics USING btree (iid);
 
 CREATE INDEX index_epics_on_last_edited_by_id ON public.epics USING btree (last_edited_by_id);
 
-CREATE INDEX index_epics_on_lock_version ON public.epics USING btree (lock_version) WHERE (lock_version IS NULL);
-
 CREATE INDEX index_epics_on_parent_id ON public.epics USING btree (parent_id);
 
 CREATE INDEX index_epics_on_start_date ON public.epics USING btree (start_date);
@@ -10079,8 +10083,6 @@ CREATE INDEX index_issues_on_duplicated_to_id ON public.issues USING btree (dupl
 
 CREATE INDEX index_issues_on_last_edited_by_id ON public.issues USING btree (last_edited_by_id);
 
-CREATE INDEX index_issues_on_lock_version ON public.issues USING btree (lock_version) WHERE (lock_version IS NULL);
-
 CREATE INDEX index_issues_on_milestone_id ON public.issues USING btree (milestone_id);
 
 CREATE INDEX index_issues_on_moved_to_id ON public.issues USING btree (moved_to_id) WHERE (moved_to_id IS NOT NULL);
@@ -10247,8 +10249,6 @@ CREATE INDEX index_merge_requests_on_head_pipeline_id ON public.merge_requests U
 
 CREATE INDEX index_merge_requests_on_latest_merge_request_diff_id ON public.merge_requests USING btree (latest_merge_request_diff_id);
 
-CREATE INDEX index_merge_requests_on_lock_version ON public.merge_requests USING btree (lock_version) WHERE (lock_version IS NULL);
-
 CREATE INDEX index_merge_requests_on_merge_user_id ON public.merge_requests USING btree (merge_user_id) WHERE (merge_user_id IS NOT NULL);
 
 CREATE INDEX index_merge_requests_on_milestone_id ON public.merge_requests USING btree (milestone_id);
@@ -11249,12 +11249,6 @@ CREATE INDEX tmp_build_stage_position_index ON public.ci_builds USING btree (sta
 
 CREATE INDEX tmp_idx_on_user_id_where_bio_is_filled ON public.users USING btree (id) WHERE ((COALESCE(bio, ''::character varying))::text IS DISTINCT FROM ''::text);
 
-CREATE INDEX tmp_index_ci_builds_lock_version ON public.ci_builds USING btree (id) WHERE (lock_version IS NULL);
-
-CREATE INDEX tmp_index_ci_pipelines_lock_version ON public.ci_pipelines USING btree (id) WHERE (lock_version IS NULL);
-
-CREATE INDEX tmp_index_ci_stages_lock_version ON public.ci_stages USING btree (id) WHERE (lock_version IS NULL);
-
 CREATE UNIQUE INDEX users_security_dashboard_projects_unique_index ON public.users_security_dashboard_projects USING btree (project_id, user_id);
 
 CREATE UNIQUE INDEX vulnerability_feedback_unique_idx ON public.vulnerability_feedback USING btree (project_id, category, feedback_type, project_fingerprint);
@@ -13989,6 +13983,14 @@ COPY "schema_migrations" (version) FROM STDIN;
 20200605093113
 20200608072931
 20200608075553
+20200608195222
+20200608205813
+20200608212030
+20200608212435
+20200608212549
+20200608212652
+20200608212807
+20200608212824
 20200608214008
 20200609002841
 20200609142506
diff --git a/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb b/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb
index 2e5e450afc7004aac5f4632ea2eba0cfd9ff1ea3..d6dd90e1320d0cb2ec6fa1753f6a8cf96178a183 100644
--- a/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb
+++ b/spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb
@@ -3,7 +3,7 @@
 require 'spec_helper'
 require Rails.root.join('db', 'post_migrate', '20200427064130_cleanup_optimistic_locking_nulls_pt2_fixed.rb')
 
-describe CleanupOptimisticLockingNullsPt2Fixed, :migration do
+describe CleanupOptimisticLockingNullsPt2Fixed, :migration, schema: 20200219193117 do
   test_tables = %w(ci_stages ci_builds ci_pipelines).freeze
   test_tables.each do |table|
     let(table.to_sym) { table(table.to_sym) }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 291cccd72db94f49646be29a01b83c6a822e07ee..63f3e4671434f854ed57adc3706f9ae4407ba51f 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -95,29 +95,6 @@
     end
   end
 
-  describe 'locking' do
-    using RSpec::Parameterized::TableSyntax
-
-    where(:lock_version) do
-      [
-        [0],
-        ["0"]
-      ]
-    end
-
-    with_them do
-      it 'works when an issue has a NULL lock_version' do
-        issue = create(:issue)
-
-        described_class.where(id: issue.id).update_all('lock_version = NULL')
-
-        issue.update!(lock_version: lock_version, title: 'locking test')
-
-        expect(issue.reload.title).to eq('locking test')
-      end
-    end
-  end
-
   describe '.simple_sorts' do
     it 'includes all keys' do
       expect(described_class.simple_sorts.keys).to include(
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c70ddac5da6bbfa97abf0d0e49d2a92f2609e7cc..dc2d760dbeb143808180976343f5f9cc37c799a2 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -55,29 +55,6 @@
     end
   end
 
-  describe 'locking' do
-    using RSpec::Parameterized::TableSyntax
-
-    where(:lock_version) do
-      [
-        [0],
-        ["0"]
-      ]
-    end
-
-    with_them do
-      it 'works when a merge request has a NULL lock_version' do
-        merge_request = create(:merge_request)
-
-        described_class.where(id: merge_request.id).update_all('lock_version = NULL')
-
-        merge_request.update!(lock_version: lock_version, title: 'locking test')
-
-        expect(merge_request.reload.title).to eq('locking test')
-      end
-    end
-  end
-
   describe '#squash_in_progress?' do
     let(:repo_path) do
       Gitlab::GitalyClient::StorageSettings.allow_disk_access do